OC底层原理(04)类的底层探究(上)

765 阅读5分钟

一 .对象isa之间的关系

通过OC对象本质与 isa的研究中,我们发现每个对象都有成员变量isa, 今天我们以此为入口来一起研究一下对象isa之间的关系。

话不多少,上代码,先创建两个类 YJPerson(继承NSObject)YJTeacher(继承YJPerson),用来测试;如下:

// YJPerson
@interface YJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

// YJTeacher
@interface YJTeacher : YJPerson
@property (nonatomic, copy) NSString *subject;
@end

1.1 元类

元类是编译器自动生成,苹果为什么要自动生成这么一个类呢?那就来探究下

Xnip2022-07-05_12-16-59.png

YJPerson类有两个不一样的地址,$0 = 0x0000000100008228 即类对象,也就是 YJPerson 的类地址;$1 = 0x0000000100008200 类对象的 isa 指向的类,这个类Apple把它叫做元类

1.2 isa 走位

/// isa 链路
void testIsaChain (id obj)
{
    // 对象所属类 - 对象的isa指向
    Class cls = object_getClass(obj);
    // 元类 - 类的isa指向
    Class metaCls = object_getClass(cls);
    // 根元类 - 元类的isa指向
    Class rootCls = object_getClass(metaCls);
    // 根根元类 - 根元类的isa指向
    Class metaOfRootCls = object_getClass(rootCls);

    NSLog(@"对象的      isa -> : %@", obj);
    NSLog(@"类的的      isa -> : %@, %p", cls, cls);
    NSLog(@"元类的      isa -> : %@, %p", metaCls, metaCls);
    NSLog(@"根元类的    isa -> : %@, %p", rootCls, rootCls);
    NSLog(@"根根元类的   isa -> : %@, %p\n", metaOfRootCls, metaOfRootCls);
}

in main(int argc, const char * argv[]) {
    @autoreleasepool {
        // isa 链路测试
        testIsaChain(NSObject.new);
        testIsaChain(YJPerson.new);
        testIsaChain(YJTeacher.new);
        NSLog(@"Hello, World!");
    }
    return 0;
}

打印结果:

1656660086668.jpg

通过打印结果,得出结论:

对象 -isa-> 类 -isa-> 元类 -isa-> 根元类 -isa-> 根元类自己

1.3 superclass 走位图

/// super class 链路
void testSuperClassChain (Class cls)
{
    Class superCls = class_getSuperclass(cls);
    NSLog(@"父类 : %@, %p", superCls, superCls);

    if (!superCls) {
        NSLog(@"\n");
        return;
    }
    testSuperClassChain(superCls);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 类的 superclass 链路
        Class objCls = NSObject.class;
        NSLog(@"类 : %@, %p", objCls, objCls);
        testSuperClassChain(objCls);

        Class personsCls = YJPerson.class;
        NSLog(@"类 : %@, %p", personsCls, personsCls);
        testSuperClassChain(personsCls);

        Class teacherCls = YJTeacher.class;
        NSLog(@"类 : %@, %p", teacherCls, teacherCls);
        testSuperClassChain(teacherCls);

        // 原类的 superclass 链路
        Class objMetaClass = object_getClass(objCls);
        NSLog(@"%@ 的元类 : %@, %p", objCls, objMetaClass, objMetaClass);
        testSuperClassChain(objMetaClass);

        Class personMetaClass = object_getClass(personsCls);
        NSLog(@"%@ 的元类 : %@, %p", personsCls, personMetaClass, personMetaClass);
        testSuperClassChain(personMetaClass);

        Class teacherMetaClass = object_getClass(teacherCls);
        NSLog(@"%@ 的元类 : %@, %p", teacherCls, teacherMetaClass, teacherMetaClass);
        testSuperClassChain(teacherMetaClass);
    }
    return 0;
}

输出结果:

1656663474113.jpg

通过分析, 我们可以发现superclass 链路可以分为两条:

  1. 类的 superclass 链路:

    -supercls-> 父类 -supercls-> ... -supercls-> NSObject类 -supercls-> nil

  2. 类的元类 superclass 链路:

    类的元类 -supercls-> 父类的元类 -supercls-> ... -supercls-> NSObject的元类 -supercls-> NSObject类 -supercls-> nil

最终指向流程图:

isa流程图.png

至此,对于对象isa的链路关系, 以及 superclass的继承链关系,已经有一个清醒的认知了。

但是,的内部成员结构是什么样的,我们仍然不知道。继续探索。。。

二. 类的结构分析

基于objc4(838版本) objc_class 源码

struct objc_class : objc_object {
    // 省略 ...
    
    // Class ISA; // isa 继承自 objc_object 8字节  
    Class superclass; // 8字节
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    // 省略 ...
    
    class_rw_t *data() const {
        return bits.data();
    }
    
    // 省略 ...
}
  • isa成员:继承自objc_objectisa,占 8字节

  • superclass 成员:Class类型,Classtypedef struct objc_class *Class;定义的,是一个指针,占8字节

  • cache 成员:简单从类型class_data_bits_t目前无法得知,而class_data_bits_t是一个结构体类型,结构体内存大小需要根据内部的成员来确定

  • bits 成员:只有首地址经过上面3个属性的内存大小总和的平移,才能获取到bits

2.1 计算 cache 的内存大小

cache_t 的定义:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
// 省略无关代码。。。

cache_t 分析:

cache_t 有两个成员:_bucketsAndMaybeMask一个联合体

  • _bucketsAndMaybeMaskuintptr_t无符长整型占8字节

  • 联合体里面有两个成员变量结构体_originalPreoptCache(联合体的内存大小由成员变量中的最大成员变量的类型决定)

    • _originalPreoptCache 是个指针占8字节

    • 结构体 中有 _maybeMask_flags_occupied

      • _maybeMaskuint32_t4字节
      • _flagsuint16_t 各占2字节
      • _occupieduint16_t 各占2字节
      • 结构体 大小是 4 + 2 + 2 = 8字节
  • cache_t的内存大小是 8 + 8 = 16 字节

2.2 获取 bits

我们已经知道了类结构中 isa(8字节)superclass(8字节)cache(16字节) 的大小,接下来只需要通过地址偏移8 + 8 + 16 = 32,即可得到 bits 的地址

Xnip2022-07-03_21-59-13.png

  • 其中的data()获取数据,是由objc_class提供的方法

Xnip2022-07-03_22-02-41.png

  • 从源码 或 lldb调试中都可以看出 bits.data() 中存储的信息,其类型是class_rw_t,也是一个结构体类型。但我们还是没有看到属性列表、方法列表等,需要继续往下探索

2.3 探索 属性列表,即 property_list

通过查看class_rw_t定义的源码发现,结构体中有提供相应的方法去获取 属性列表、方法列表等,如下所示:

Xnip2022-07-03_22-10-43.png

获取bits并打印bits信息的基础上,通过class_rw_t提供的方法,继续探索 bits中的属性列表,以下是lldb 探索的过程图示:

Xnip2022-07-03_23-08-02.png

  • p $2.properties()命令中的propertoes方法是由class_rw_t提供的,返回值类型是 property_array_t

  • list 的类型是 property_list_t,是一个指针,所以通过 p *$5获取内存中的信息,同时也证明 bits中存储了 property_list,即属性列表

2.4 探索 方法列表,即 methods_list

在前文中提到的 YJPerson 中添加两个方法:实例方法sayHello,类方法 eat

// YJPerson.h
@interface YJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)sayHello;
+ (void)eat;
@end

// YJPerson.m
@implementation YJPerson
- (void)sayHello { }
+ (void)eat { }
@end
2.4.1 方法的获取

参照属性列表的获取,我们来获取方法列表,如下图:

Xnip2022-07-04_10-17-01.png

这里和属性获取不太一样,属性列表 property_list_t里存储的是 property_t,而 property_t 是这样的:

struct property_t {
    const char *name;
    const char *attributes;
};

所以属性通过索引可以直接输出属性的信息。方法列表method_list_t 存储的是 method_tmethod_t 是这样的:

struct method_t {
    // 省略。。。
    // 方法描述结构体
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    // 省略。。。
    // big()方法 返回方法描述结构体
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
    // 省略。。。
}

所以需要在获取 method_t 之后再调用 big() 方法,才能正常输出

2.4.2 方法分析

Xnip2022-07-04_10-24-30.png

通过打印 method_list_t 中的每个 method_t,发现并没有类方法eat;由此可知:method_list_t 存储的是类的对象方法。那么 类方法eat存在哪儿呢?不捉急,后续篇章为您揭秘。。。

总结

类中有isasuperclasschchebits 成员变量,在对bits 探究过程中发现bits存储着大家熟悉的属性列表方法列表成员变量列表协议列表等,打开了我们对的认知,后面会继续对类进行探究,敬请期待