OC 对象(instance、class、meta_class)内存结构

263 阅读6分钟

isa 指针

OC 的实例对象、类对象以及元类对象,内部都有一个 isa 指针,那么这些类型的 isa 指针有什么作用?或者说分别指向哪里?

我们知道 OC 方法调用在编译时都会转化为 C 函数 objc_msgSend 的调用。 比如 Person 类,有一个实例方法 instanceMethod 和一个类方法 classMethod

Person *p = [[Person alloc] init];
// 对象方法调用
[p instanceMethod];
    
// 类方法调用
[Person classMethod];

objc_msgSend(p, @selector(instanceMethod));其中调用的实例方法 instanceMethod 存储在类对象中,给 p 对象发送消息时,如何找到实例方法的?同样给类对象 Person 发送的类方法 classMethod 是存储在元类对象相中,消息发送时又是如何找到元类对象查找方法的? 这其实是通过 isa 完成的,即:

  • instance 的 isa 指向 class:
    当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。

  • class 的 isa 指向 meta-class:
    当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用。

superclass

类对象的 superclass

假设 Male类 继承 Person类,Person类 继承 NSObject。那么:
1)Male类 信息中的 superclass指针 指向 Person类;
2)Person类 信息中的 superclass指针 指向 NSObject,

类对象的 superclass指针 指向父类的类对象;
superclass 在实例对象方法调用过程:
当子类的的 instance 对象想要调用父类的的对象方法时,会先通过 isa 找到子类的 class,然后通过 superclass 找到父类的 class,最后找到对象方法的实现进行调用。

元类对象的 superclass

元类对象的 superclass指针 指向父类的元类对象;
superclass 在类方法调用过程:
当子类的 class 要调用父类的类方法时,会先通过 isa 找到子类的m eta-class,然后通过 superclass 找到父类的 meta-class,最后找到类方法的实现进行调用。

isa 和 superclass 总结

引用经典的一张图:

  • instance 的 isa 指向 class

  • class 的 isa 指向 meta-class

  • meta-class 的 isa 指向基类的 meta-class
    Subclass(meta) 和 Superclass(meta) 的 isa 都指向 Rootclass(meta),同样 Rootclass(meta) 的 isa 指向 Rootclass(meta)。

  • class 的 superclass 指向父类的 class
    如果没有父类,superclass指针为nil

  • meta-class的superclass指向父类的meta-class
    基类的 meta-class 的 superclass 指向基类的 class。即图中的 Rootclass 的元类对象的 superclass 指向的是 Rootclass 的类对象

  • instance 调用对象方法的轨迹
    isa 找到 class,方法不存在,就通过 superclass 找父类...一直到 superclass 为 nil 为止;

  • class 调用类方法的轨迹
    isa 找 meta-class,方法不存在,就通过superclass找父类...一直到 superclass 为 nil 为止;

注意:由于基类的 meta-class 的 superclass 指向基类的 class。所以类方法调用的时候,当沿着 superclass 找到基类元类对象《Rootclass(meta)》,如果基类的元类对象中无该类方法,会继续沿着 superclass 找到基类的类对象《Rootclass(class)》,在基类的类对象中查找对象方法,找到则会调用对象方法。

ps:上面两个方法调用轨迹,只是为了说明 isa 和 superclass 在方法调用中的应用场景,只是大致的调用轨迹。

针对上面 class 调用类方法的轨迹 的注意事项,代码测试:

为 NSObject 添加分类,并添加类方法 test 的申明,不做类方法的实现,实现同名对象方法:

NSObject+Category.h 中
@interface NSObject (Category)

+ (void)test;

@end


NSObject+Category.m 中

@implementation NSObject (Category)

- (void)test
{
    NSLog(@"[NSObject test] - 实例方法");
}

@end

Person 类继承 NSObject,只申明类方法 test 而不实现

Person.h 中
@interface Person : NSObject

+ (void)test;

@end

Person.m 
@implementation Person

@end

测试类方法调用:

- (void)viewDidLoad {
    [super viewDidLoad];
    [Person test];
}

打印日志:

Isa_Test01[33875:3708779] [NSObject test] - 实例方法

可以看到,调用 Person 的类方法(+ (void)test),最终来到 NSObject 的 对象方法实现中。

进一步探讨 isa

前面我们总结到:instance 的 isa 指向 class;class 的 isa 指向 meta-class。那么 isa 指针值等于类对象或者元类对象的地址么?
通过代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    Class personClass = [Person class];
    Class personMetaClass = object_getClass(personClass);
    NSLog(@"实例对象地址: %p", person);
    NSLog(@"类对象地址: %p", personClass);
    NSLog(@"元类对象地址: %p", personMetaClass);
}

断点调试,分别打印实例对象 person 的 isa 的地址值,和类对象 personClass 的地址值

可以看到真机上,person->isa 的地址 0x000001a102dad579 不等于 Person 类对象的地址 0x0000000102dad578。

person->isa 与(&)ISA_MASK之后的值才为 Person 类对象的地址值。

在 objc 源码中对于 ISA_MASK 的定义:

所以在 iPhone 上(arm64架构),ISA_MASK 取值 0x0000000ffffffff8ULL,继续调试,将 person->isa的地址 & 0x0000000ffffffff8ULL,结果为0x0000000102dad578,和 Person类对象 地址相同。

按照统一的操作打印类对象的 isa,控制台报错:

此时可以自定义 struct cw_objc_class,同源码的 struct objc_class 结构体类型相似的结构体类型:

struct cw_objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

并将类对象(personClass)强制转换成自定义的 struct cw_objc_class

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *person = [[Person alloc] init];
    Class personClass = [Person class];
    struct cw_objc_class *cw_personClass = (__bridge struct cw_objc_class *)(personClass);
    Class personMetaClass = object_getClass(personClass);
}

再次断点调试:

可以看到类对象 personClass 的 isa 与(&) ISA_MASK 即为 元类对象 personMetaClass 的地址值。

所以准确的说:对象或者类对象的 isa 指针间接指向 类对象或者元类对象,即需要将 isa & ISA_MASK 才是类对象或者元类对象的地址。

而对于 superclass 可以验证,类对象或者元类对象的 superclass 直接 指向父类类对象或者父类元类对象。

Class 的结构

类对象、元类对象在内存中的结构是一样的,接下来从底层源码了解类对象或者元类对象的信息。

类对象、元类对象的类型都是 Class:

typedef struct objc_class *Class;

因此只需要去了解 struct objc_class,但直接从 xcode 看到struct objc_class 的定义中注释:OBJC2 已不可用

所以想要了解 struct objc_class 需要去 objc 源码中查看:

而其继承的 objc_object 中有个成员变量 isa:

因此 struct objc_class 的结构体内存可以大致理解为:
注意:只是说可以这样理解。

struct objc_class {
    Class isa;
    Class superclass;
    cache_t cache;  // 方法缓存
    class_data_bits_t bits; // 用于获取具体的类信息
}

其中获取具体的类信息 class_data_bits_t bits,是通过调用 data() 方法获取:

并且 bits & FAST_DATA_MASK 得到 struct class_rw_t

struct class_rw_t 的定义:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;         // 方法列表
    property_array_t properties;    // 属性列表
    protocol_array_t protocols;     // 协议列表

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
}

其中 const class_ro_t *ro 的信息为:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance 对象所占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;  // 类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成员变量列表

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

总结:
1、类对象class、元类对象meta-class 的本质结构都是 struct objc_class:

其中 instance 中的其他成员变量,是指成员变量的具体值。

2、struct objc_class 在内存中的结构为:

_