iOS实例对象,类对象,元类对象源码解析

519 阅读5分钟

一.首先我们来看一下怎么获取对象的实例对象,类对象,元类对象

实例对象通过alloc,new获取,例:

NSObject *instanceObjc = [[NSObject alloc] init];

类对象通过class方法或者object_getClass(需要导入#import <objc/runtime.h>)获取,例:

Class classObjc = object_getClass(instanceObjc);

Class classObjc2 = [instanceObjc class];

元类对象通过object_getClass获取,例:

Class metalClass = object_getClass(classObjc);

二.解析实例对象,类对象,元类对象的数据结构

实例对象解析:

我们创建一个Person类,新增成员变量_no和属性name

@interface Person : NSObject
{
    int _no;
}

@property (nonatomic, copy) NSString *name;

@end

通过c++代码转换后,我们可以看到Person的数据结构为

struct Person_IMPL {

    struct NSObject_IMPL NSObject_IVARS;
    
    int _no;
    
    NSString *_name;
};

struct NSObject_IMPL {
    Class isa;
};

通过源码解析我们可以看到实例对象包含了一个Class指针isa以及成员变量及属性,isa指针我们放在后面统一说,这里先简单认为可以通过isa指针找到类对象

类对象及元类对象解析:

我们可以发现类对象及元类对象的类型是一样的,都是Class类型,接下来我们通过runtime源码去解析一下

在objc文件里可以看到Class类型其实是struct objc_class

typedef struct objc_class *Class;

struct objc_object {

    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

};

接下来我们全局搜索struct objc_class,发现有多个文件包含,我们查看objc-runtime-new,这个文件里面是最新的,我们发现类对象和元类对象的结构体如下(这里只展示我们需要的信息,想查看完整信息可以去查看源码)

struct objc_object {
    isa_t isa;
}

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
    
    class_rw_t *data() { 
        return bits.data();
    }
}

通过这一段数据结构可以看到类对象及元类对象包含了一个isa指针,一个superclass指针,一个cache,一个bits,接下来我们来一一解析

在这里给大家放一张网上流传比较广的图,便于我们更好的认识isa指针和superclass指针

5b9989fd8b1578b38c873aefa16a4ad6.jpeg

图片中虚线就是isa指针,实线是superclass指针

isa指针解析:

可以很清晰的看到,实例对象的isa指针指向类对象,类对象的指针指向元类对象,元类对象的isa指针指向基类的元类对象(注意这里在64位之前isa是直接存储对应的地址值,例如实例对象的isa直接存储类对象的地址值,但是在64位之后,需要isa&ISA_MSAK才能获取类对象的地址值)

superclass指针解析:

实例对象没有superclass指针,类对象的superclass指针指向类对象的父类一直到基类,元类对象的superclass指针指向元类对象的父类直到基类,元类对象的基类对象的superclass指针指向类对象的基类对象(也就是图中的RootClass(meta)指向RootClass(class))

cache解析:

查看runtime源码中的struct cache_t

struct cache_t {

    struct bucket_t *_buckets;//散列表

    mask_t _mask;//散列表的长度-1

    mask_t _occupied;//已经缓存的方法数量
}

可以看到这个里面主要是存储了方法的缓存,缓存列表_buckets是一个散列表或者叫哈希表,假如我们之前调用过这个方法的话,就会直接存在缓存里,下次直接从缓存取就可以,大大加快了方法调用的速度

bits解析:

class_rw_t *data() { 
    return bits.data();
}

class_rw_t* data() {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}

可以看到bits通过&FAST_DATA_MASK可以得到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; }

可以看到包含了一个class_ro_t的数据结构,方法信息(类对象是实例方法,元类对象是类方法),属性信息,协议信息,第一个子类,下一个兄弟类,猜测class_rw_t是设计一种二叉树的结构待验证,接下来继续查看class_ro_t

**struct** class_ro_t {

    uint32_t flags;

    uint32_t instanceStart;

    uint32_t instanceSize;

    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;

};

可以看到这里存储的是成员变量信息列表(实例对象存储的是成员变量具体的值,这里存储的是成员变量的描述信息),类名,base方法列表,base协议列表,base属性列表。

总结:实例对象存储了isa指针及成员变量的值,类对象存储了实例方法,成员变量信息,协议信息,属性信息等,元类对象存储了类方法列表。

三:通过分析方法调用来加深一下上面的印象

假如我们给Person类添加了一个实例方法和类方法

@interface Person : NSObject

- (void)instanceMethodTest;

+ (void)classMethodTest;

@end

执行实例方法

Person *p = [[Person alloc] init];
[p instanceMethodTest];

这里会先拿到实例对象p的isa指针,通过&ISA_MSAK找到Person的类对象(因为实例方法存储在类对象里),看看有没有实现,如果实现则存入cache,下次直接从缓存中获取,如果没有实现,会通过superClass指针去父类中找,如果父类实现则直接调用,如果没有实现则继续往上找(这也就是为什么子类可以调用父类的方法),直到基类,如果基类还没有实现,则会走消息转发机制,如果没有处理,则抛出错误unrecognized selector sent to instance****

执行类方法

[Person classMethodTest];

这里会先拿到类对象Person的isa指针,通过&ISA_MASK找到Person的元类对象(因为类方法存储在元类对象里),看看有没有实现,如果实现则存入cache,下次直接从缓存中获取,如果没有实现,会通过superClass指针去父类中找,如果父类实现则直接调用,如果没有实现则继续往上找,直到基类,如果基类也没有实现,会去Person基类的类对象中去查找,如果还没有找到,则会走消息转发机制,如果没有处理,则抛出错误unrecognized selector sent to instance****