美文网首页
OC底层原理(四)、isa走向与class_data_bits_

OC底层原理(四)、isa走向与class_data_bits_

作者: 默默_David | 来源:发表于2020-09-20 17:48 被阅读0次

    OC底层原理汇总

    上一篇中,我们对isa的初始化、类与对象的底层结构以及属性进行了简单剥析。

    • 对于isa,我们得出结论,isa是一个存储了所属类的地址;
    • 对于类的底层结构,我们得出了类有四个成员,isasuperclasscachebits
    • 对于属性,我们得出了属性是由实例变量+setter+getter组成。

    在这一篇中,我们将进一步探析:

    • isa指向的类的isa指向什么呢,如果一直指向下去,最终会指向哪里?
    • 对于cachebits,它们分别是用来做什么的呢?它们内部结构是什么样的呢?

    我们接下来逐一进行分析

    一、isa指向

    1.使用lldb命令分析isa指向

    我们在main.m中,首先创建一个LWPerson类继承自NSObject,然后再创建一个LWStudent继承自LWPerson,并在main函数中创建两个类的实例,在创建后打一个断点,如下图所示:

    创建两个类并添加断点

    上一篇中我们知道,isa.bits & ISA_MASK就可以获得isa指向的类的地址,据此,我们使用lldb来调试查找isa的走位,如下面代码所示

    //16进制打印student的地址
    (lldb) p/x student 
    (LWStudent *) $0 = 0x0000000100546690
    //根据student的内容连续打印四段,第一段就是isa.bits
    (lldb) x/4gx $0
    0x100546690: 0x001d8001000023d5 0x00000000003c0001
    0x1005466a0: 0x0000000100001030 0x0000000000000457
    //得到isa指向的类的地址
    (lldb) p/x 0x001d8001000023d5 & 0x00007ffffffffff8ULL
    (unsigned long long) $1 = 0x00000001000023d0
    //po之后,可以看到指向LWStudent
    (lldb) po $1
    LWStudent
    
    //继续找LWStudent的isa指向
    (lldb) x/4gx $1
    0x1000023d0: 0x00000001000023a8 0x0000000100002380
    0x1000023e0: 0x00000001005466f0 0x0002802c00000007
    (lldb) p/x 0x00000001000023a8 & 0x00007ffffffffff8ULL
    (unsigned long long) $2 = 0x00000001000023a8
    //po 之后发现,也是指向LWStudent
    (lldb) po $2
    LWStudent
    
    //继续查找isa指向
    (lldb) x/4gx $2
    0x1000023a8: 0x00007fff991e60f0 0x0000000100002358
    0x1000023b8: 0x0000000100546770 0x0003e03500000007
    (lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $3 = 0x00007fff991e60f0
    //此处发现指向NSObject
    (lldb) po $3
    NSObject
    
    //继续查找NSObject的isa指向
    (lldb) x/4gx $3
    0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
    0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
    (lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $4 = 0x00007fff991e60f0
    (lldb) po $4
    //发现,NSObject的isa还是指向NSObject
    NSObject
    
    //我们继续查找
    (lldb) x/4gx $4
    0x7fff991e60f0: 0x00007fff991e60f0 0x00007fff991e6118
    0x7fff991e6100: 0x0000000100704fb0 0x0004e03100000007
    (lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $5 = 0x00007fff991e60f0
    (lldb) po $5
    //发现仍然是NSObject,并且地址都是0x00007fff991e60f0
    NSObject
    

    上方的lldb指令大致流程就是从查找studentisa指向出发,不断的查找指向的指向,其大致流程图如下
    [图片上传失败...(image-e690ab-1600595328194)]
    从案例中我们可以发现,对象studentisa指向Student,而Studentisa也是指向的StudentStudentisa指向NSObject,而NSObjectisa指向自己。
    在这里,连续出现了两个Student,这两个的地址相差40个字节,并不是同一个,为什么会出现两个不同的Student呢?

    为了分析这个问题,我们使用lldb命令,新创建一个NSObject类的对象,继续查找是否会出现两个NSObject,如下所示

    //创建一个NSObject的对象
    (lldb) expr [NSObject alloc]
    (NSObject *) $6 = 0x000000010071b990
    (lldb) po $6
    <NSObject: 0x10071b990>
    
    (lldb) x/4gx $6
    0x10071b990: 0x001dffff991e6119 0x0000000000000000
    0x10071b9a0: 0x656b6f54534e5b2d 0x7420646c6569466e
    (lldb) p/x 0x001dffff991e6119 & 0x00007ffffffffff8ULL
    //找到它的指向,地址为0x00007fff991e6118
    (unsigned long long) $7 = 0x00007fff991e6118
    (lldb) po $7
    NSObject
    
    //继续查找NSObject的isa指向
    (lldb) x/4gx $7
    0x7fff991e6118: 0x00007fff991e60f0 0x0000000000000000
    0x7fff991e6128: 0x0000000100630340 0x0004801000000007
    (lldb) p/x 0x00007fff991e60f0 & 0x00007ffffffffff8ULL
    //找到NSObject的isa指向,地址为0x00007fff991e60f0
    (unsigned long long) $8 = 0x00007fff991e60f0
    (lldb) po $8
    NSObject
    

    测试后可以看出,NSObject也有两个,两者的地址相差也是40个字节,其最终指向的NSObject的地址为0x00007fff991e60f0,与我们之前指令得到的最终指向NSObject的地址相同。

    2.通过调用runtime API来分析isa走向

    我们在main函数中编辑代码如下:

    int main(int argc, const char * argv[]) {
        
        LWStudent *student = [LWStudent alloc];
        
        Class class1 = object_getClass(student);
        NSLog(@"class1 is %@",class1);
        
        Class class2 = object_getClass(class1);
        NSLog(@"class2 is %@",class2);
        
        Class class3 = object_getClass(class2);
        NSLog(@"class3 is %@",class3);
        
        Class class4 = object_getClass(class3);
        NSLog(@"class4 is %@",class4);
        
        Class class5 = object_getClass(class4);
        NSLog(@"class5 is %@",class5);
        
        return 0;
    }
    //打印结果
    class1 is LWStudent
    class2 is LWStudent
    class3 is NSObject
    class4 is NSObject
    class5 is NSObject
    

    结果与lldb调试得到的结果一致

    3.isa指向图、类对象、元类

    综合以上分析,我们得到了这张经典图


    isa指向经典图

    在之前的测试中,我们得到两个类分别称之为类对象与元类,类对象是元类的对象,而NSObject的元类我们称之为根元类

    这时,isa的指向我们可以总结为:对象->类对象->元类->根元类->根元类根元类继承于NSObject,它的isa指向自身

    4.分析类结构体struct objc_class结构

    同时,根据我们之前的测试,类对象的地址比它的元类高40字节,我们根据struct objc_class的结构进行分析。

    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        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();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    }
    

    其中cache_t是一个结构体,它内部所有的成员变量如下:

    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;
        explicit_atomic<mask_t> _mask;
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
    #else
    #error Unknown cache mask storage type.
    #endif   
        uint16_t _flags;
        uint16_t _occupied;
    }
    

    其中,uintptr_tunsigned long的别名,占8个字节mask_tuint_32_t的别名,占4个字节
    另外,我们看到,cache_t内部针对不同平台使用了不同的存储结构,我们可以在objc-runtime-new.h中看到对于各个存储类型的定义

    #define CACHE_MASK_STORAGE_OUTLINED 1
    #define CACHE_MASK_STORAGE_HIGH_16 2
    #define CACHE_MASK_STORAGE_LOW_4 3
    
    #if defined(__arm64__) && __LP64__
    //arm64架构且是64位Unix系统,也就是我们现在的iOS设备
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
    #elif defined(__arm64__) && !__LP64__
    //arm64架构但不是64位Unix系统,iphone 5s之前的设备都是32位
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
    #else
    //其它,MAC_OS设备
    #define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
    #endif
    

    回到我们的cache_t结构体中,无论是哪种平台,根据内存对齐规则,它需要的存储空间都是16字节

    我们再看class_data_bits_t的结构,它内部的结构比较简单

    struct class_data_bits_t {
        uintptr_t bits;
    }
    

    所以,class_data_bits_t bits占8字节。

    综上所述,struct objc_class所占的内存如下图所示,正好是40个字节

    类结构字节占用图

    所以,我们得出结论,在内存中,类对象元类的存储是紧密挨在一起的。

    二、class_data_bits_t

    接下来我们分析class_data_bits_t,在objc781源码中,class_data_bits_t和以前有了很大的改变,现在的源码中,它只有一个成员bits

    • 想要获取class_rw_t,可以通过struct objc_class的常成员函数class_rw_t* data()来获取;或者通过地址偏移先获得class_data_bits_t,再通过它的常成员函数class_rw_t* data()来获取。

    • 想要获取class_ro_t,可以通过struct class_data_bits_t的成员方法const class_ro_t *safe_ro()获取;或者在获取到class_rw_t的数据后,使用struct class_rw_t的常成员函数class_ro_t *ro()来获取。

    1.地址偏移

    在测试class_data_bits_t之前,我们先介绍一下什么是地址偏移

    有如下C语言的代码:

    int a[] = {0,1,2,3,4,5,6,7,8,9};
    int temp1 = a[4];
    int temp2 = *(a+4);
    

    我们知道,C语言中,数组名就是指向数组首地址的指针,我们在获取数组元素的时候,可以使用下标获取a[4],也可以通过指针偏移的方式*(a+4),因为a有被定义为了int类型,所以,加一个偏移量就是偏移一个int的字节大小,在上例中,a[4]*(a+4)获取到的值相同,都是获取数组第5个元素的值。

    内存中,我们要读取内存中的值也可以使用这样的方式,由于地址是没有类型的,所以我们的偏移量需要直接设置为多少字节

    如我们有了struct objc_class的指针,根据我们之前对它内部结构的分析,我们获取它指向的内容地址后,可以如下得到各个成员的地址:

    • 前面8个字节就是isa
    • 偏移8个字节可以得到父类指针superclass
    • 偏移16个字节可以得到缓存cache
    • 偏移32个字节可以得到class_data_bits_t类型的bits数据

    下面我们根据这个思想使用lldb找到class_data_bits_t,并且分析它内部的成员。

    2.lldb分析class_data_bits_t

    1).设置调试案例

    我们打开可编译的源码,在main.m中添加代码如下:

    @interface LWPerson : NSObject{
        NSInteger a;
        NSString *b;
        CGFloat c;
    }
    //姓名
    @property (nonatomic,copy) NSString *name;
    //年龄
    @property (nonatomic,assign) short age;
    //性别
    @property (nonatomic,assign) BOOL isMan;
    
    @end
    
    @implementation LWPerson
    
    - (void)sayHello{
        NSLog(@"%s",__func__);
    }
    - (void)sayByeBye{
        NSLog(@"%s",__func__);
    }
    + (void)eat{
        NSLog(@"%s",__func__);
    }
    + (void)drink{
        NSLog(@"%s",__func__);
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        
        LWPerson *person = [[LWPerson alloc]init];
        person.name = @"Jobs";
        person.age = 77;
        person.isMan = YES;
        
        [person sayHello];
        [person sayByeBye];
        [LWPerson eat];
        [LWPerson drink];
        
        return 0;
    }
    
    2).lldb调试查看class_rw_t结构

    我们在[LWPerson drink];前添加一个断点进行调试。

    //获取person的类LWPerson的地址
    (lldb) p/x person.class
    (Class) $0 = 0x0000000100002368 LWPerson
    //地址偏移32个字节获取class_data_bits_t,16进制下就是加上20
    (lldb) p (class_data_bits_t *)0x0000000100002388
    (class_data_bits_t *) $1 = 0x0000000100002388
    //调用class_data_bits_t的成员方法data()获取到class_rw_t的指针
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x0000000100655680
    (lldb) p *$2
    //查看class_rw_t的内容
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975656
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    3).class_ro_tclass_rw_t

    关于class_ro_tclass_rw_t的探索过程我们以后再去探究,关于这两个结构体,我们先大致解释一下:

    • class_ro_t是编译期生成的,它存储了当前类在编译期就已经确定的属性方法以及协议,它里面是没有分类中定义的方法协议的。
    • class_rw_t是在运行时生成的,它在realizeClass中生成,它包含了class_ro_t。它在_objc_init方法中关于dyld的回调的map_images中最终将分类的方法协议都插入到自己的方法列表协议列表中。它不包含成员变量列表,因为成员变量列表是在编译期就确定好的,它只保存在class_ro_t中。不过,class_rw_t中包含了一个指向class_ro_t的指针。

    class_rw_t的类结构中,有如下一些成员函数:

    struct class_rw_t {
    
    public:
        //获取class_ro_t
        const class_ro_t *ro() const {
            auto v = get_ro_or_rwe();
            if (slowpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>()->ro;
            }
            return v.get<const class_ro_t *>();
        }
        //设置class_ro_t
        void set_ro(const class_ro_t *ro) {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                v.get<class_rw_ext_t *>()->ro = ro;
            } else {
                set_ro_or_rwe(ro);
            }
        }
        //获取方法列表
        const method_array_t methods() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
            }
        }
        //获取属性列表
        const property_array_t properties() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>()->baseProperties};
            }
        }
        //获取协议列表
        const protocol_array_t protocols() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
            }
        }
    };
    

    根据这些暴露出来的方法,我们继续使用lldb查看class_rw_t的内容。

    4).寻找实例方法

    我们继续进行lldb调试,先看看有哪些方法

    //获取方法列表
    (lldb) p $3.methods()
    (const method_array_t) $4 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000020f0
          arrayAndFlag = 4294975728
        }
      }
    }
    //读取实际的方法列表(list)
    (lldb) p $4.list
    (method_list_t *const) $5 = 0x00000001000020f0
    //读取list内部的结构
    (lldb) p *$5
    (method_list_t) $6 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 9
        first = {
          name = "sayHello"
          types = 0x0000000100000ebb "v16@0:8"
          imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
        }
      }
    }
    //获取第一个方法,看得出来是- (void)sayHello
    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "sayHello"
      types = 0x0000000100000ebb "v16@0:8"
      imp = 0x0000000100000b90 (LWObjc`-[LWPerson sayHello] at main.m:27)
    }
    //获取第二个方法,看得出来是- (void)sayByeBye
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = "sayByeBye"
      types = 0x0000000100000ebb "v16@0:8"
      imp = 0x0000000100000bc0 (LWObjc`-[LWPerson sayByeBye] at main.m:30)
    }
    ////获取第三个方法,看得出来是isMan的getter方法
    (lldb) p $6.get(2)
    (method_t) $9 = {
      name = "isMan"
      types = 0x0000000100000efd "c16@0:8"
      imp = 0x0000000100000c90 (LWObjc`-[LWPerson isMan] at main.m:21)
    }
    //获取第四个方法,看得出来是isMan的setter方法
    (lldb) p $6.get(3)
    (method_t) $10 = {
      name = "setIsMan:"
      types = 0x0000000100000f05 "v20@0:8c16"
      imp = 0x0000000100000cb0 (LWObjc`-[LWPerson setIsMan:] at main.m:21)
    }
    //获取第五个方法,看得出来是c++析构方法
    (lldb) p $6.get(4)
    (method_t) $11 = {
      name = ".cxx_destruct"
      types = 0x0000000100000ebb "v16@0:8"
      imp = 0x0000000100000cd0 (LWObjc`-[LWPerson .cxx_destruct] at main.m:25)
    }
    //获取第六个方法,看得出来是name的getter方法
    (lldb) p $6.get(5)
    (method_t) $12 = {
      name = "name"
      types = 0x0000000100000ed7 "@16@0:8"
      imp = 0x0000000100000bf0 (LWObjc`-[LWPerson name] at main.m:17)
    }
    //获取第七个方法,看得出来是name的setter方法
    (lldb) p $6.get(6)
    (method_t) $13 = {
      name = "setName:"
      types = 0x0000000100000edf "v24@0:8@16"
      imp = 0x0000000100000c20 (LWObjc`-[LWPerson setName:] at main.m:17)
    }
    //获取第八个方法,看得出来是age的getter方法
    (lldb) p $6.get(7)
    (method_t) $14 = {
      name = "age"
      types = 0x0000000100000eea "s16@0:8"
      imp = 0x0000000100000c50 (LWObjc`-[LWPerson age] at main.m:19)
    }
    //获取第九个方法,看得出来是age的setter方法
    (lldb) p $6.get(8)
    (method_t) $15 = {
      name = "setAge:"
      types = 0x0000000100000ef2 "v20@0:8s16"
      imp = 0x0000000100000c70 (LWObjc`-[LWPerson setAge:] at main.m:19)
    }
    //获取第十个方法时提示越界
    (lldb) p $6.get(9)
    Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    

    从结果我们可以看出,所有的实例方法和属性的settergetter方法都存放在类对象的class_rw_t的方法列表中

    方法types v16@0:8的解释:
    v表示方法返回值是void16表示方法所有参数一共占16字节,由于所有OC方法有两个默认的参数id selfSEL _cmd@表示对象类型,也就是id self0表示这个参数从0字节位置开始,:表示方法,也就是SEL _cmd8表示这个参数从第8个字节位置开始

    5).探索属性列表

    我们再看看属性列表

    //获取属性列表
    (lldb) p $3.properties()
    (const property_array_t) $16 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          list = 0x0000000100002298
          arrayAndFlag = 4294976152
        }
      }
    }
    //读取实际的属性列表
    (lldb) p $16.list
    (property_list_t *const) $17 = 0x0000000100002298
    //我们在读取属性列表的时候被打断了,没有权限
    (lldb) p *$17
    error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
    
    6).探索成员变量列表

    在前面的我们提到,成员变量在class_ro_t中,而不在class_rw_t中,以为编译结束类的结构就已经确定了,不能够再被改变。

    所以,我们先从class_rw_t获取到class_ro_t,再查找ivars

    //得到`class_ro_t`
    (lldb) p $3.ro()
    (const class_ro_t *) $19 = 0x00000001000020a8
    //查看`class_ro_t`结构
    (lldb) p *$19
    (const class_ro_t) $20 = {
      flags = 388
      instanceStart = 8
      instanceSize = 48
      reserved = 0
      ivarLayout = 0x0000000100000e4b "\x11!"
      name = 0x0000000100000e42 "LWPerson"
      baseMethodList = 0x00000001000020f0
      baseProtocols = 0x0000000000000000
      ivars = 0x00000001000021d0
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000100002298
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    //获得ivars
    (lldb) p $20.ivars
    (const ivar_list_t *const) $21 = 0x00000001000021d0
    //查看ivars
    (lldb) p *$21
    (const ivar_list_t) $22 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 6
        first = {
          offset = 0x0000000100002310
          name = 0x0000000100000e58 "a"
          type = 0x0000000100000ec3 "q"
          alignment_raw = 3
          size = 8
        }
      }
    }
    //获取第一个成员变量,这个是a
    (lldb) p $22.get(0)
    (ivar_t) $23 = {
      offset = 0x0000000100002310
      name = 0x0000000100000e58 "a"
      type = 0x0000000100000ec3 "q"
      alignment_raw = 3
      size = 8
    }
    //获取第二个成员变量,这个是b
    (lldb) p $22.get(1)
    (ivar_t) $24 = {
      offset = 0x0000000100002318
      name = 0x0000000100000e5a "b"
      type = 0x0000000100000ec5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    //获取第三个成员变量,这个是c
    (lldb) p $22.get(2)
    (ivar_t) $25 = {
      offset = 0x0000000100002320
      name = 0x0000000100000e5c "c"
      type = 0x0000000100000ed1 "d"
      alignment_raw = 3
      size = 8
    }
    //获取第四个成员变量,这个是_isMan
    (lldb) p $22.get(3)
    (ivar_t) $26 = {
      offset = 0x0000000100002328
      name = 0x0000000100000e5e "_isMan"
      type = 0x0000000100000ed3 "c"
      alignment_raw = 0
      size = 1
    }
    //获取第五个成员变量,这个是_age
    (lldb) p $22.get(4)
    (ivar_t) $27 = {
      offset = 0x0000000100002330
      name = 0x0000000100000e65 "_age"
      type = 0x0000000100000ed5 "s"
      alignment_raw = 1
      size = 2
    }
    //获取第六个成员变量,这个是_name
    (lldb) p $22.get(5)
    (ivar_t) $28 = {
      offset = 0x0000000100002338
      name = 0x0000000100000e6a "_name"
      type = 0x0000000100000ec5 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    //获取第七个报错,提示我们越界了
    (lldb) p $22.get(6)
    Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    

    3.类方法存储位置的探索

    在上面的案例探索中,我们都没有发现类方法+ (void)eat+ (void)drink,这是为什么呢?

    第一部分中,我们谈了isa指向的问题,在我们的方法查找的过程中,我们的实例方法存放在类对象中,这部分我们已经验证了。而类方法保存在元类方法列表中。

    我们接下来对此进行验证

    //得到LWPerson的元类
    (lldb) p/x object_getClass(person.class)
    (Class) $0 = 0x0000000100002340
    (lldb) po $0
    LWPerson
    //加上32字节,16进制下就是加20,得到class_data_bits_t
    (lldb) p (class_data_bits_t *)0x0000000100002360
    (class_data_bits_t *) $1 = 0x0000000100002360
    //获取class_rw_t
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x000000010111a7c0
    //查看class_rw_t结构
    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975528
      }
      firstSubclass = nil
      nextSiblingClass = 0x00007fff8b25dcd8
    }
    //获取方法列表
    (lldb) p $3.methods()
    (const method_array_t) $4 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100002070
          arrayAndFlag = 4294975600
        }
      }
    }
    //获取实例的方法列表list
    (lldb) p $4.list
    (method_list_t *const) $5 = 0x0000000100002070
    (lldb) p *$5
    (method_list_t) $6 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 2
        first = {
          name = "eat"
          types = 0x0000000100000ebb "v16@0:8"
          imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
        }
      }
    }
    //获取第一个方法,我们发现是类方法eat
    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "eat"
      types = 0x0000000100000ebb "v16@0:8"
      imp = 0x0000000100000b30 (KCObjc`+[LWPerson eat] at main.m:33)
    }
    //获取第二个方法,我们发现是类方法drink
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = "drink"
      types = 0x0000000100000ebb "v16@0:8"
      imp = 0x0000000100000b60 (KCObjc`+[LWPerson drink] at main.m:36)
    }
    //获取第三个方法时提示越界了
    (lldb) p $6.get(2)
    Assertion failed: (i < count), function get, file .../runtime/objc-runtime-new.h, line 438.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    

    这就是验证了类方法存放在元类方法列表

    三、总结

    在本篇中,我们研究了isa的走向以及对class_data_bits_t的结构进行了分析。

    • isa的走向为:对象->类对象->元类->根元类->根元类根元类继承于NSObject,它的isa指向自身

    • class_data_bits_t中只有一个bits,它通过算法可以得到class_rw_tclass_rw_t存储了了类的方法列表、属性列表、协议列表;而成员变量存放在class_ro_t中。实例方法存放在类对象方法列表中,类方法存放在元类方法列表中。

    下一篇我们继续分析关于类结构中的cache_t的作用于内部结构与算法。

    相关文章

      网友评论

          本文标题:OC底层原理(四)、isa走向与class_data_bits_

          本文链接:https://www.haomeiwen.com/subject/gcdpyktx.html