Objective-C 中 对象/类/元类的本质

184 阅读5分钟

1. Class (类)

1. Class (类)的本质

objc/runtime.h 中,Class(类) 被定义为指向 objc_class 结构体的指针,objc_class 结构体的数据结构如下:

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // objc_class 结构体的实例指针
#if !__OBJC2__
    Class _Nullable super_class;                                 // 指向父类的指针
    const char * _Nonnull name;                                  // 类的名字
    long version;                                                // 类的版本信息,默认为 0
    long info;                                                   // 类的信息,供运行期使用的一些位标识
    long instance_size;                                          // 该类的实例变量大小;
    struct objc_ivar_list * _Nullable ivars;                     // 该类的实例变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
    struct objc_cache * _Nonnull cache;                          // 方法缓存
    struct objc_protocol_list * _Nullable protocols;             // 遵守的协议列表
#endif
};

objc_class 结构体的第一个成员变量是 isa 指针isa 指针 保存的是objc_class结构体的实例指针,换言之,Class(类)的本质就是一个对象,称之为 类对象

2. Method (方法)

objc_class 结构体的 struct objc_method_list **methodLists(方法列表)结构体用来存储方法的列表

struct objc_method_list {
    struct objc_method_list * _Nullable obsolete;
    int method_count;
#ifdef __LP64__
    int space;
#endif
    /* variable length structure */
    struct objc_method method_list[1];
}           

objc/runtime.h 中, Method(方法)被定义为指向objc_method 结构体的指针,objc_method 结构体数据结构:

typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name;                    // 方法名
    char * _Nullable method_types;               // 方法类型
    IMP _Nonnull method_imp;                     // 方法实现
};

objc_method结构体中包含了 method_name(方法名),method_types(方法类型:方法的参数类型和返回值类型) 和 method_imp(方法实现)。MethodSEL(方法声明) 和 IMP(方法实现)关联起来(键值对:SEL: IMP),当对一个对象发送消息时,会通过给出的 SEL(方法声明)去找到 IMP(方法实现),然后执行。

1. SEL (方法声明)

objc/objc.h中, SEL(方法声明)被定义为指向 objc_selector结构体的指针。

typedef struct objc_selector *SEL;

runtime相关头文件中并没有找到明确的定义。通过测试得出: SEL 只是一个保存方法名的字符串。Objective-C 在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是 SEL。

2. IMP (方法实现)

objc/objc.h中,IMP(方法实现)被定义为指向函数的指针。

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。

IMP 指向的函数函第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是 SEL 类型的方法选择器,接下来是方法的实际参数列表,并返回一个 id。通过一组 idSEL 参数就能确定唯一的方法实现地址。也就是说 IMP 是消息最终调用的执行代码,是方法真正的实现代码 。

3. method_types (方法类型)

method_types 是个字符串,用来存储方法的参数类型和返回值类型。

3. Ivar (实例变量)

objc_class 结构体的 struct objc_ivar_list *ivars(实例变量列表)中结构体用来存储成员变量的列表。

struct objc_ivar_list {
    int ivar_count;
#ifdef __LP64__
    int space;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1];
}        

objc/runtime.h 中, Ivar(实例变量)被定义为指向objc_ivar 结构体的指针,objc_ivar 结构体数据结构:

typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name;	//实例变量名称  
    char * _Nullable ivar_type;	//实例变量类型  
    int ivar_offset;						//基地址偏移字节
#ifdef __LP64__
    int space;
#endif
}     
4. Cache (方法缓存)

objc_class 结构体的 struct objc_cache *cache(方法缓存)。在 objc/runtime.h 中,Cache(方法缓存)被定义为指向objc_cache 结构体的指针,objc_cache 结构体数据结构:

typedef struct objc_cache *Cache
struct objc_cache {
    unsigned int mask /* total = mask + 1 */;
    unsigned int occupied;
    Method _Nullable buckets[1];
}

Cache为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在Cache中查找。

Runtime系统会把被调用的方法存到Cache中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。

5. Protocol (协议)

objc_class 结构体的struct objc_protocol_list *protocols(遵守的协议列表),中存放的元素就Protocol(协议);

typedef struct objc_object Protocol;
struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

objc/runtime.h 中, Protocol(协议)被定义为objc_object结构体。

#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif

2. Object (对象)

objc/objc.h 中,Object(对象)被定义为 objc_object 结构体,其数据结构如下:

struct objc_object {
    Class _Nonnull isa;  // objc_class 结构体的实例指针  
};

typedef struct objc_object *id;

objc_object 结构体只包含一个 Class 类型的 isa 指针。即, objc_object 结构体唯一保存的就是它所属 Class(类)的地址。

3. Meta Class (元类)

对象(objc_object 结构体)isa 指针 指向的是对应的 类对象(object_class 结构体)。那么 类对象(object_class 结构体)的 isa 指针 又指向 类对象 自身的 Meta Class(元类)

Runtime 中把类对象所属类型就叫做 Meta Class(元类),用于描述类对象本身所具有的特征,而在元类的 methodLists 中,保存了类的方法链表,即所谓的「类方法」。并且类对象中的 isa 指针 指向的就是元类。每个类对象有且仅有一个与之相关的元类。

4. 对象、类、元类之间的关系

  1. isa 指针

    1. 实例对象的 isa 指针 指向对应的 类对象类对象isa 指针 指向对应的 元类。(所有)元类的 isa 指针 最终指向 根(NSObject)元类
    2. 注意:(所有)元类包括根(NSObject)元类,即,根元类 的 isa 指针 指向自己。
  2. 父类指针

  3. 类对象父类指针指向父类对象父类对象父类指针指向根类(NSObject)对象根类(NSObject)对象父类指针指向nil;

  4. 元类父类指针指向父类对象的元类,父类对象的元类父类指针指向根(NSObject)元类,根(NSObject)元类父类指针指向根类(NSObject)对象, 根类(NSObject)对象父类指针指向 nil 。