iOS 底层分析

iOS 底层 - 名词解析

2019-12-05  本文已影响0人  He_Define

目录

名词解析

1. 源码 <objc/objc.h>/<objc-private.h> 中的名词

① 实例对象 ===> id 和结构体 objc_object
/// A pointer to an instance of a class.
typedef struct objc_object *id;

源码指出,id 是指向 objc_object 的指针。而注释告诉我们,id 就是一个指向某个类的实例对象的指针。那 objc_object 就是某个类的实例对象,如下:

// <objc/objc.h> 源码中
// Objective-C 2.0 已废弃  
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};


// <objc-private.h> 源码中
// Objective-C 2.0 支持的定义
struct objc_object {
private:
    isa_t isa;
public:
    Class ISA();
    Class getIsa();
    void initIsa(Class cls /*nonpointer=false*/);
    void initClassIsa(Class cls /*nonpointer=maybe*/);
    void initProtocolIsa(Class cls /*nonpointer=maybe*/);
    void initInstanceIsa(Class cls, bool hasCxxDtor);
    ...
}

objc_object 表示一个类的实例objc_object 里面有一个 isa_t 类型的 isa 指针,这个指针指向了这个对象的类对象

② 类 ==== Class 和结构体 objc_class
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

从源码中可以看出,Class 是一个指向 objc_class 结构体的指针.注释告诉我们,Class 表示一个 Objective-C 类。那么 objc_class 又是什么?

// <objc/runtime.h> 源码中
// Objective-C 2.0 已经废弃
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
....
#endif
} OBJC2_UNAVAILABLE;


// <objc-runtime-new.h> 源码中
// Objective-C 2.0 支持
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits;    
    class_rw_t *data() { 
        return bits.data();
        // 这个结构体里有所有的变量列表,方法列表等
    }
    ...
}

我们可以从源码中看出来, objc_class 结构体继承自 objc_object. 说明 objc_class 也是个对象。那我们就很简单得推断出 objc_class 也有个 isa 指针。那么这个 isa 指针又指向的是什么呢?

其实这里仅仅从源码来看,我们并不知道这里的isa指向了哪里,反正不是父类,因为父类下面还有个 superclass 指向了它的父类
那 isa 指向了哪里呢? 只能通过网上的解答来解释了:
isa指向的是它的 metaclass(元类) , 元类也是 objc_class 的另一种对象


关于类和对象

我们常说的对象,一般指的是 实例对象。 但其实对象不仅仅是实例对象,还有其他的, 比如类对象. 我们上面有说到,objc_classobjc_object 几乎一致,那我们从 runtime 的源码下继续深入研究中看到

// <objc-runtime-new.h> 源码
struct objc_class : objc_object {
    ...
}

从这里看出,objc_class 是继承了 objc_object 的结构体。这里的源码我们其实可以先得出一个结论:
类也是一个对象

关于 metaclass 元类

在国外大牛的文章:What is a meta-class in Objective-C? 中对 metaclass 的解释总结过来有三个要点:

  1. metaclass 元类也是一个对象,是一个 obj_class 的一种,即是类对象的一种。这意味着, 你也可以对这些元类作为对象来发送消息。也因此每个Class都有其 metaclass 原类
  2. 所有的 metaclass 元类都使用同一个基础类的 元类。这意味着 几乎所有的metaclass 元类都是 ‘NSObject的元类’ 的派生类。
  3. 几乎所有的 metaclass元类 上的 isa 指针 指向的都是 ‘NSObject的元类’.元类也有个 isa 指针指向 root meteClass 根元,根元的 isa 指针指向自己

最后来一张经典图片收尾



③ 方法选择器 ==== SEL 和结构体 objc_selector
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SELobjc_selector 结构体的指针,表示了一个方法的选择器。结构体的内容在 runtime.hobj.h 的源码中无法找到,所以我们无法得知 objc_selector 到底是什么。不过很多 Blog 和书籍中指出: " SEL 其实也是其中一种数据类型,用来定义类方法,是类成员方法的指针,本质上其实只是一串字符串。”(其实可以看做是类方法的identifier),接下来验证这种说法是否正确

// 首先,我们先定义一个 SEL, 定义 SEL 可以有多种方式
SEL aSelector = @selector(SEL selector);

SEL bSelector = SEL sel_registerName(const char *name);
// (NSSelectorFromString 方法就是通过 sel_registerName 方法来注册的 SEL)
SEL cSelector = NSSelectorFromString(NSString *cSelectorName);
// 其实从定义我们就可以看出来, SEL 和 字符串 可能存在某种转化。

// 除此之外, 我们输出下这个 SEL 发现:
// 尽管报了 warning ,但是你还是可以看到输出的就是个字符串,而没有报错
NSLog(@"%s",aSelector);


// 另外,我们还可以通过另一个方法来看看 SEL 其实也是个类似Key的东西
// 举例, dog 和 cat 是两个不同的实例对象,都有一个名为 description 的方法
SEL selector1 = @selector(description);
[dog performSelector:selector1];    // 调用 dog 的 description 方法
[cat performSelector:selector1];    // 调用 cat 的 description 方法
// 可以看出虽然 SEL 虽然相同, 但是调用的方法是不同的。

那么其实 我们是否可以大胆得得出一个结论:SEL 其实就只是 一个方法名 或者说 标识. 这一点其实从
后面的 objc_method 结构体中 SEL method_name 命名可以看出。

④ 方法实现 ==== IMP
#if !OBJC_OLD_DISPATCH_PROTOTYPES
// 指向C的函数
typedef void (*IMP)(void /* id, SEL, ... */ );                  // 无返回值
#else
// OC的指向方法的指针
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);    // 有返回值
#endif

IMP(Implementation)是一个指向 方法实现的地址 的指针,参数都包含 idSEL 类型。通过 id (实例对象)里的 SEL (方法名) 获取到唯一的实现方法。

IMP imp = class_getMethodImplementation(Class cls, SEL name)
IMP imp = method_getImplementation(Method _Nonnull m) 
// 无参无返回值的 IMP 调用。    (其实走的是C语言的指向函数)
imp();

// 对于有参数有返回值的 IMP 的调用比较麻烦。
// 这里给出一种常规操作: 强制类型转换 (例子中返回值和参数的类型都是 int)
int (*imp)(id, SEL, int) ;
imp = (int (*)(id, SEL, int))method_getImplementation(method); // 强制类型转换
Object *obj = [[Object alloc] init];
NSLog(@"%d", imp(obj, @selector(method_name:), 3));

当然,IMP还有更底层更广泛的应用

其实,直接调用方法的 IMP指针效率比调用方法本身更高。但是也不要盲目得去用IMP来提高速率,毕竟可读性肯定没有那么高了。

⑤ 布尔值 ==== bool 和 BOOL 和 Boolean、true 和 false 、 YES 和 NO
// bool 的定义 在 <stdbool.h> 中 原代码较长,总结下来是这样的
// 如果是 C 语法
#define bool _Bool // 因为 C 语言 不具有 bool 值的,因此 C99 用 _Bool 来兼容 C++
#define true 1
#define false 0
// 如果是 C++ 语法
#define bool  bool
#define false false
#define true  true
  1. 这里注意, bool 不管在 C 还是在 C++, 都是只有 1个字节
  2. bool 值为 true/false, 对应的值只有 1/0
  3. 所有的 非0 数值, 强制转换成 bool 类型 都会转换成 1/true 。 如下:
NSLog(@"%d %d %d %d %d", (bool)1, (bool)-1, (bool)0, (bool)256, (bool)8);
//输出 1, 1, 0, 1, 1
// BOOL 的定义 在 <objc.h> 中
// OBJC_BOOL_IS_BOOL 的定义省略. 总结就如下
#if OBJC_BOOL_IS_BOOL // 当 64位iPhone 或 ARMv7K 时
    typedef bool BOOL;
#else // 当是 32位iPhone 以及 非ARMv7K 时
    typedef signed char BOOL; 
#endif

// objc_bool 的 类型与 系统位数有关
#if __has_feature(objc_bool) 
#define YES __objc_yes   // __objc_yes = signed char 1 (32位) / true (64位)
#define NO  __objc_no    // __objc_no = signed char 0 (32位) / false (64位)
#else
#define YES ((BOOL)1)
#define NO  ((BOOL)0)
#endif
  1. BOOL 不管是32位系统还是64位系统,都占了 1个字节
  2. 32 位系统 的类型是 signed char . 字符空间只有8位,超过8位的都只会取低8位的值。如 256 = 0b1 0000 0000 转换成 BOOL 会等于 0
  3. 所有的 非0 数值, 强制转换成 BOOL 类型,都不会转换成 1/true
NSLog(@"%d %d %d %d %d", (BOOL)1, (BOOL)-1, (BOOL)0, (BOOL)256, (BOOL)8);
//输出 1 -1 0 0 8

注意: 这里不会转换成0/1 会导致一个问题。如果在条件判断语句中,使用 condition == YES,或 condition != YES这种写法,等式会不成立。比如:

BOOL condition = 123;                               BOOL condition = 123;
if (condition == YES) {                             if (condition) {
    // 不会走这里                                       // 走这里, 正确
} else {                     ==用该方法改进==>        } else {
    // 走的是这里. 这是错误的
}                                                   }
  • 总结来说
  • 判断语句中不要用 == YES 的方式
  • 尽量避免赋值给 BOOL 。 如果要赋值给 BOOL 也请注意取值范围 (因为超过8位会截取)
// Boolean 的定义在 <MacTypes.h> 中
typedef unsigned char Boolean;

Boolean 的注释中 有说到 Mac OS historic type, sizeof(Boolean)==1 说明Boolean只有一个字节

区别 nil Nil NULL 和 NSNull
// 源码中的 nill Nil 在 <objc.h>
// Nil 定义
#ifndef Nil
# if __has_feature(cxx_nullptr)
#   define Nil nullptr
# else
#   define Nil __DARWIN_NULL
# endif
#endif

// nil 定义
#ifndef nil
# if __has_feature(cxx_nullptr)
#   define nil nullptr
# else
#   define nil __DARWIN_NULL
# endif
#endif

// NULL 在 C 语言中的定义
#undef NULL
#if defined(__cplusplus)
#define NULL 0
#else
#define NULL ((void *)0)
#endif

// NSNull
@interface NSNull : NSObject <NSCopying, NSSecureCoding>
+ (NSNull *)null;
@end

2. 源码<objc/runtime.h>/<objc-runtime-new.h>中的名词

① 方法 ==== Method 和结构体 method_t / objc_method
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_method *Method;
struct objc_method {
    SEL _Nonnull method_name                            OBJC2_UNAVAILABLE;
    char * _Nullable method_types                       OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                             OBJC2_UNAVAILABLE;
}OBJC2_UNAVAILABLE;

已经在 Objective-C 2.0 中废弃的,我们就不看了。重点关注下面的源码

// <objc-runtime-new.h> 源码中
typedef struct method_t *Method;

struct method_t {
    SEL name;               // 方法名
    const char *types;      // 返回值和参数
    MethodListIMP imp;      // 指向“方法的实现”的指针  注: using MethodListIMP = IMP;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    }; // 根据name的地址对方法进行排序的函数
};

这里我们可以知道 一个 Method 需要具备以下几个条件: 方法名、参数、返回值、实现方法
不同的类可以有相同的 Method 方法。查找 Method 的时候会在类的 Method 链表中进行查找。
定位到一个 Method 一般有下面两种:

// 获取实例方法
Method *method = class_getInstanceMethod([ClassName class], @selector(method_name))
// 获取类方法
Method *method = class_getClassMethod([ClassName class], @selector(method_name))

常用的方法

// 给类添加一个方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
// 替换 cls 类中的 name 方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 交换 m1, m2 方法
method_exchangeImplementations(Method  m1, Method  m2)
// 设置 指向方法的实现 指针。 返回 原来的指向方法实现的指针
IMP method_setImplementation(Method method, IMP imp)
② 成员变量 ==== Ivar 和结构体 ivar_t / objc_ivar
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_ivar *Ivar;
struct objc_ivar {
    char *ivar_name;  //实例变量名
    char *ivar_type;  //实例变量类型类型
    int ivar_offset;  //基地址偏移字节
    int space;
}

以上源码已经在Objective-C 2.0中被废弃

// <objc-private.h>
typedef struct ivar_t *Ivar;

// <objc-runtime-new.h> 源码中
struct ivar_t {
    int32_t *offset;            // 基地址 偏移字节
    const char *name;           // 实例变量名
    const char *type;           // 实例变量类型
    uint32_t alignment_raw;     //对齐
    uint32_t size;              //大小
    uint32_t alignment() const {
        if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
        return 1 << alignment_raw;
    }
};

Ivar 是指向 对象的实例变量。Ivar 是指向 ivar_t 结构体 的指针,从结构体看出,Ivar 包括类型和名字等。

// 1. 获取 Ivar 实例变量列表
unsigned int count;
Ivar *ivarList = class_copyIvarList([Class class], &count);
// 这里如果想要知道每一个实例变量。用个 for循环 就可以了
for (int i = 0; i < count; i ++) {
    Ivar ivar = ivarList[i];
}
free(ivarList);  // 结束后要释放

// 2. 获取 Ivar 实例变量
Ivar ivar = class_getInstanceVariable([Class class], ivar_name);

// 3. 获取 结构体对应的值
ivar_getName(Ivar ivar)              // 获取 实例变量名
ivar_getTypeEncoding(Ivar ivar);     // 获取 实例类型
ivar_getOffset(Ivar ivar);           // 获取 实例偏移值

// 4.获取/设置 变量
object_setIvar(id obj, Ivar ivar, id value)     // 设置某个对象的 实例变量的值
object_getIvar(id obj, Ivar ivar)               // 获取某个对象的 实力变量的值

// 5.变量的添加 (无法在运行时添加)
class_addIvar(Class cls, const char *name, size_t size,alignment, const char *types)

这里有个注意点,很多人发现变量添加 会失败。因为 无法通过运行时添加ivar 或者说动态添加类的时,已经添加完成的类中,无法新增 Ivar
所以注释中有说 class_addIvar 需要 objc_allocateClassPair 和 objc_registerClassPair 之间。

③ 属性 objc_property_t 和结构体 property_t / objc_property
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_property *objc_property_t;

// <objc-private.h> 源码中的定义
typedef struct property_t *objc_property_t;
// <objc-runtime-new.h> 源码: 
struct property_t {
    const char *name;               // 属性名
    const char *attributes;         // 属性的特性
};
// 1. 获取 属性 的列表
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([Class class], &count);
for (int i = 0; i < count; i++) {
    objc_property_t property = proList[i];
}
free(propertyList);

// 2. 根据属性名 获取 objc_property_t
class_getProperty(Class cls, const char* name)

// 3. 获取结构体的内容
property_getName(objc_property_t property)          // 获取属性名
property_getAttributes(objc_property_t property)    // 获取属性的特性

// 4. 对属性进行修改或新增
class_replaceProperty(Class cls, 
                      const char* name, 
                      const objc_property_attribute_t* attributes, 
                      unsigned int attributeCount)      // 替换属性
class_addProperty(Class cls, 
                  const char * name,
                  const objc_property_attribute_t*  attributes,
                  unsigned int attributeCount)          // 新增属性(同Ivar 一致)

// 5.属性 的特性 相关
// 获取属性的特性列表
property_copyAttributeList(objc_property_t property, unsigned int *outCount)
// 拷贝属性的特性的值
property_copyAttributeValue(objc_property_t property, const char *attributeName)

注意,这里的 新增属性方法 在动态添加类结束后 也会失败

name attribute
T 类型,如@"NSString"
& retain
W weak
N nonatomic
R readonly
C copy
G(name) getter=(name)
S(name) setter=(name)
D @dynamic
V 实例变量Ivar
P 用于垃圾回收机制
关于 Ivar(实例) 和 Property(属性) 的区别

这里有人会奇怪,Ivar (实例) 和 Property (属性) 有什么区别?
当你定义个一个新的属性时, @property NSString* newProperty;
你会发现多了一个实例变量 _newProperty 和两个方法 setter 和 getter 方法
所以我们 其实可以看做 Property = Ivar + setter + getter

④ 分类 ==== Category 和结构体 category_t 和 objc_category
// <runtime.h> 源码中 (Objective-C 2.0 已废弃)
typedef struct objc_category *Category;
struct objc_category {
    char * _Nonnull category_name                         OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                            OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods     OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols       OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;

上面的源码已经在 Objective-C 2.0 中废弃.

// <objc-private.h> 和 <objc-runtime-new.h>源码中
typedef struct category_t *Category;
struct category_t {
    const char *name;                               // 分类名
    classref_t cls;                                 // 类                         
    struct method_list_t *instanceMethods;          // 实例方法 列表
    struct method_list_t *classMethods;             //  类方法 列表
    struct protocol_list_t *protocols;              //   协议  列表
    struct property_list_t *instanceProperties;     //  实例属性 列表
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;       //  类属性 列表

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

关于 category 的 实现原理,我们会在后面讲到。

小结

1. 关于博客中的源码内容
在查看 <objc/runtime.h> 的过程中,我发现头文件源码中很多都有用到宏定义 OBJC2_UNAVAILABLE 以及用 #if !__OBJC2__ endif 来区别是否是 Objective-C 2.0+ , 那么也就是说我们看到的这些方法其实很多都已经不适用于当前的 Objective-C 版本,最新的很多都看不到了。不过 runtime 的源代码苹果已经开源。我们可以从官网下载到源码 ->源码下载地址。在源码中我们可以看到这些对象里面都有些什么。

注意: 正如上面所说很多 <runtime.h><objc.h> 的大部分都已经在 Objective-C 2.0 的时候过期了。所以我们 Objective-C 2.0 相对应的 <objc-runtime-new.h><objc-private.h>文件等。 objc4源码 中也可以看到

2. 关于使用哪种方式来定义属性的类型
我注意到在源码 <objc/runtime.h> 的注释中有写到 /* Use Class' instead of 'struct objc_class *' */, 可以推断,苹果其实都不建议直接用结构体指针来命名该类型类型的值。

参考

  1. runtime官方源码下载地址
  2. 深入理解objc中的类与对象
  3. 轻松学习之 IMP指针的作用
上一篇下一篇

猜你喜欢

热点阅读