美文网首页
《OC底层系列五》-类的结构分析

《OC底层系列五》-类的结构分析

作者: 002and001 | 来源:发表于2020-10-14 15:53 被阅读0次

    前言

    • 通过上一篇 《OC底层系列四》-isa&superclass分析》中我们分析了isa和superclass的走向,知道了:
      • OC类的isa(其位域成员shiftcls)存储着其元类的信息
      • NSObject类的superclass指向nil,NSObject元类的superclass指向NSObject类
    • 今天对类的结构进行分析,探究类的属性、成员变量、实例方法、类方法在底层的实现。

    目录

    目录.png

    1、简介

    本文主要从结合底层源码,结合lldb来分析探究类的属性、成员变量、实例方法、类方法在底层的实现。

    2、类的结构分析

    OC类的底层为objc_class的结构体,对类进行探索,即分析objc_class包含成员的和作用。
    781源码中关于objc_class定义如下:

    // objc-runtime-new.h
    struct objc_class : objc_object {
        // Class ISA; 继承自objc_object,包含isa
        Class superclass;  // 父类指针
        cache_t cache;             // formerly cache pointer and vtable,用于缓存指针和 vtable 
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
                                   // class_rw_t 指针加上 rr/alloc 的标志
                                   // bits 用于存储类的方法、属性、遵循的协议等信息的地方
        // 返回一个 class_rw_t 类型的结构体变量
        // Objective-C 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
         class_rw_t *data() const {
            return bits.data();
        }
    ...
    }
    

    我们要探究的类的属性、方法等都在bits里面,为了能够获取到bit的内存信息,我们需要计算出isa、superclass、cache占的大小,然后运用内存偏移获取到bits内存信息。

    2.1、isa

    isa继承自objc_object,因此第1个成员为isa,前面我们分析过其占8字节

    2.2、superclass

    superclass为Class类型,即一个指向objc_class结构体的指针,占8字节

    // objc.h
    typedef struct objc_class *Class;
    

    2.3、cache

    是一个cache_t的成员,cache_t定义关键代码如下如下:

    struct cache_t {
    // 非ARM设备
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;  // 结构体指针,8字节
        explicit_atomic<mask_t> _mask; // mask_t即uint32_t类型,4字节
    
    // ARM64位
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets;  // uintptr_t即unsigned long类型,8字节
        mask_t _mask_unused; // mask_t即uint32_t类型,4字节
    
    // 非ARM64位,即ARM32位
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
        // _maskAndBuckets stores the mask shift in the low 4 bits, and
        // the buckets pointer in the remainder of the value. The mask
        // shift is the value where (0xffff >> shift) produces the correct
        // mask. This is equal to 16 - log2(cache_size).
        explicit_atomic<uintptr_t> _maskAndBuckets; // uintptr_t即unsigned long类型,8字节
        mask_t _mask_unused; // mask_t即uint32_t类型,4字节
    #else
    #error Unknown cache mask storage type.
    #endif
      
    // uint16_t即unsigned short类型,占2字节
    
    // 64位系统下
    #if __LP64__
        uint16_t _flags;
    #endif
    
        uint16_t _occupied;
    

    cache_t是一个结构体,缓存指针和函数表,由注释可以知道,无论是ARM64、ARM32还是模拟器环境下,其成员所占字节依次为8、4、2、2字节,根据内存对齐(参考带你深入理解iOS-内存对齐)可以知道,cache占16字节

    2.4、bits

    • 综上我们可以知道,将类的首地址偏移32字节即可得到bits的内存地址信息。
    • bits是一个class_data_bits_t类型的结构体指针,根据内存偏移可以得出class_data_bits_t的地址为:
      • 方式一:通过**p/x Person.class **计算得出类首地址然后+32得出。
      • 方式二:使用x/6gx 获取其类首地址开始6个8字节可以得出其内存地址

    对Person和main做了修改,重新运行了项目,对应的内存地址发生了变换,但是不影响对bits的分析:

    // Person.h
    @interface Person : NSObject
    {
        NSString *_title;
    }
    
    @property(nonatomic,copy) NSString *name;
    
    - (void)updateTitle:(NSString *)title;
    
    @end
    
    // Person.m
    @implementation Person
    
    - (void)updateTitle:(NSString *)title {
        _title = title;
    }
    
    @end
    
    // main.m
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            Person *p1 = [[Person alloc] init];
            p1.name = @"jack";
            [p1 updateTitle:@"Teacher"];
            
            NSLog(@"Hello, World!,p1:%p",[p1 class]);
            
        }
        return 0;
    }
    

    使用方式二,log处添加断点,lldb验证如下:

    // 打印Person首地址开始的6个8字节内存空间存储的值
    (lldb) x/6gx Person.class
    // 1-16字节
    0x100008208: 0x00000001000081e0 0x0000000100333028
    // 16-32字节
    0x100008218: 0x0000000102028c10 0x0001802400000007
    // 32-48字节,0x100008208即bits的内存地址,0x100008228即bits的内存地址
    0x100008228: 0x0000000102028654 0x00000001000ac900
    // 强转为class_data_bits_t指针
    (lldb) p (class_data_bits_t *)0x100008208
    (class_data_bits_t *) $1 = 0x0000000100008228
    // 获取bits的data()
    (lldb) p/x  $1->data()
    (class_rw_t *) $2 = 0x0000000101b04670
    // 打印bits中的data信息
    (lldb) p *$2
    class_rw_t) $3 = {
      flags = 2148007936
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000232
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    

    我们获取了objc_classdata()方法返回的内容,但是没有看到属性列表、方法等,data()返回一个class_rw_t的结构体指针,关键代码定义如下

    // objc-runtime-new.h
    struct class_rw_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 *>();
        }
    
      // 存储类的实例方法
      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来进行验证。

    2.4.1 属性

    class_rw_tproperties() 方法返回一个property_array_t继承自list_array_tt的c++类,类的属性就存储在list中。

    class list_array_tt {
    ...
    private:
        union {
            List* list;
            uintptr_t arrayAndFlag;
        };
    ...
    }
    

    lldb验证:

    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000232
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    // 返回类中的属性列表信息,一个property_array_t类
    (lldb) p $3.properties()
    (const property_array_t) $4 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          list = 0x00000001000081a0
          arrayAndFlag = 4295000480
        }
      }
    }
    
    // 返回数组list的指针
    (lldb) p $4.list
    (property_list_t *const) $5 = 0x00000001000081a0
    
    // 获取数组list存储的信息
    (lldb) p *$5
    (property_list_t) $6 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1
        first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
      }
    }
    
    // 获取第一个属性
    (lldb) p $6.get(0)
    (property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
    // 获取第二个属性,越界,说明不存在
    (lldb) p $6.get(1)
    Assertion failed: (i < count), function get, file /Users/a002/PrivateReposity/github/xxerz/objc4/objc4-781/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.
    
    • 通过上面的lldb打印结果,可以知道类的属性存储路径为:
      Person类属性列表->objc_class.bits->class_rw_t.properties()->property_list_t.list->list,通过objc_class中的bits获取到类的属性列表
    2.4.2 成员变量

    通过class_rw_t中的ro()方法获取,ro()返回一个class_ro_t结构体指针,其class_ro_t结构体中的ivars存储类的成员变量列表
    class_ro_t定义如下:

    // objc-runtime-new.h
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #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;
    

    验证ivars中存储着类的成员变量:

    // 获取class_rw_t中的ro()返回值,返回一个结构体指针
    lldb) p $3.ro()
    (const class_ro_t *) $8 = 0x00000001000080a8
    // 打印class_ro_t结构体的内容
    (lldb) p *$8
    (const class_ro_t) $9 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100003f7f "\x02"
      name = 0x0000000100003f78 "Person"
      baseMethodList = 0x00000001000080f0
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100008158
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000081a0
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    // 获取类的成员变量列表的指针
    (lldb) p $9.ivars
    (const ivar_list_t *const) $10 = 0x0000000100008158
    (lldb) p *$10
    // 获取成员变量列表
    (const ivar_list_t) $11 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x00000001000081d0
          name = 0x0000000100003f1a "_title"
          type = 0x0000000100003f89 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    // 获取第一个成员变量
    (lldb) p $11.get(0)
    (ivar_t) $12 = {
      offset = 0x00000001000081d0
      name = 0x0000000100003f1a "_title"
      type = 0x0000000100003f89 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    // 获取第二个成员变量
    (lldb) p $11.get(1)
    (ivar_t) $13 = {
      offset = 0x00000001000081d8
      name = 0x0000000100003f21 "_name"
      type = 0x0000000100003f89 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    
    • 通过p $9.ivars获取到了成员变量列表的指针。
    • p *$10中获取成员变量列表,其存储在ivar_list_t结构体中,count=2说明有2个成员变量,依次打印为_title,_name
    • 我们可以得到Person类的成员变量存储路径为:
      Person类成员变量列表->objc_class.bits->class_rw_t.ro()->class_ro_t.ivars->ivars
    2.4.3 实例方法

    class_rw_tmethods()返回该一个method_array_t类,其list成员中可以获取到类的实例方法。
    验证过程如下:

    // 获取class_rw_t中的methods返回值,返回一个method_array_t的类,继承自list_array_tt
    (lldb) p $3.methods()
    (const method_array_t) $14 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000080f0
          arrayAndFlag = 4295000304
        }
      }
    }
    
    // 获取method_array_t中的list指针
    (lldb) p $14.list
    (method_list_t *const) $15 = 0x00000001000080f0
    
    // 打印list内容,list即存储着类的实例方法信息
    (lldb) p *$15
    (method_list_t) $16 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        // 有4个实例方法  
        count = 4
        first = {
          name = ".cxx_destruct"
          types = 0x0000000100003f81 "v16@0:8"
          imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
        }
      }
    }
    
    (lldb) p $16.get(0)
    (method_t) $17 = {
      name = ".cxx_destruct"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
    }
    (lldb) p $16.get(1)
    (method_t) $18 = {
      name = "name"
      types = 0x0000000100003fa0 "@16@0:8"
      imp = 0x0000000100003dd0 (objc4-test`-[Person name])
    }
    (lldb) p $16.get(2)
    (method_t) $19 = {
      name = "setName:"
      types = 0x0000000100003f95 "v24@0:8@16"
      imp = 0x0000000100003e00 (objc4-test`-[Person setName:])
    }
    (lldb) p $16.get(3)
    (method_t) $20 = {
      name = "updateTitle:"
      types = 0x0000000100003f95 "v24@0:8@16"
      imp = 0x0000000100003d80 (objc4-test`-[Person updateTitle:])
    }
    
    • 通过p *$15获取到了类的实例方法列表** method_list_t**。
    • p *$15打印方法列表信息,由count=4得知有4个方法,依次为
      -[Person .cxx_destruct]、-[Person . name]、-[Person . setName:]、-[Person .updateTitle:]
    2.4.4 类方法
    • 类的实例方法存储在类中,那么类的类方法存储在哪里?
    • 前面分析isa走位的时候提到了元类类的isa指向元类
      类的实例的isa指向类,且类的实例方法存储在类的bits中
      我们可以推测类的类方法存储在元类的bits里
      验证如下:
    // 获取类的指针
    lldb) p/x Person.class
    (Class) $0 = 0x0000000100008208 Person
    
    // 获取类的内存信息
    (lldb) x/4gx 0x0000000100008208
    0x100008208: 0x00000001000081e0 0x0000000100333028
    0x100008218: 0x0000000100693f70 0x0001802400000007
    // 获取元类的指针
    (lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
    (unsigned long long) $1 = 0x00000001000081e0
    
    // 获取元类的内存信息
    (lldb) x/6gx 0x00000001000081e0
    0x1000081e0: 0x0000000100333000 0x0000000100333000
    0x1000081f0: 0x0000000100693ff0 0x0001e03500000007
    // 0x100008200即元类的bits指针
    0x100008200: 0x0000000100693e94 0x00000001000081e0
    
    // 强转为class_data_bits_t *类型
    (lldb) p (class_data_bits_t *)0x100008200
    (class_data_bits_t *) $2 = 0x0000000100008200
    
    // 获取元类的bits信息
    (lldb) p $2->data()
    (class_rw_t *) $3 = 0x0000000100693e90
    
    // 打印元类的bits信息
    (lldb) p *$3
    (class_rw_t) $4 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000128
        }
      }
      firstSubclass = nil
      nextSiblingClass = 0x00007fff900cdcd8
    }
    // 获取元类的示例方法列表,返回一个method_array_t类
    (lldb) p $4.methods()
    (const method_array_t) $5 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100008088
          arrayAndFlag = 4295000200
        }
      }
    }
    
    // 获取method_array_t中list的内容,返回一个method_list_t结构体指针
    (lldb) p $5.list
    (method_list_t *const) $6 = 0x0000000100008088
    // 打印list内容
    (lldb) p *$6
    (method_list_t) $7 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        // 有一个实例方法
        count = 1
        first = {
          name = "eat"
          types = 0x0000000100003f81 "v16@0:8"
          imp = 0x0000000100003d70 (objc4-test`+[Person eat])
        }
      }
    }
    (lldb) p $7.get(0)
    (method_t) $8 = {
      name = "eat"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003d70 (objc4-test`+[Person eat])
    }
    
    
    • 结果证明了类的类方法存储在其元类的bits里Person元类中的示例方法只有一个,为+[Person eat]

    3、总结

    • 本文使用lldb命令分析了类的属性、成员变量、实例方法、类方法在OC底层如何获取。
    • 类的属性:通过@property定义的属性,存储在类的bits里,路径为
      objc_class.bits->class_data_bits_t.data()->class_rw_t.properties()->property_array_t.list->list
    • 类的成员变量:通过{}定义的成员变量,存储在类的bits里,路径为
      objc_class.bits->class_data_bits_t.data()->class_rw_t.ro()->class_ro_t.ivars->ivars
    • 类的实例方法:存储在类的bits里路径为
      objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list
    • 类的类方法:存储在元类bits里,路径为
      该类的元类的objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list

    相关文章

      网友评论

          本文标题:《OC底层系列五》-类的结构分析

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