美文网首页
类,类结构分析

类,类结构分析

作者: 丸疯 | 来源:发表于2020-11-04 16:08 被阅读0次

    忙不是不学习的借口

    isa和类的关联中我们知道isa中存储着类信息,今天我们就来探索一下类与类的结构。

    准备工作

    • 自定义一个继承NSObject的类Person
    //.h文件
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject{
        NSString *name;
    }
    @property (nonatomic, strong) NSString * hobby;
    
    + (void)sayHello;
    - (void)sayGood;
    
    @end
    
    //.m文件
    #import "Person.h"
    
    @implementation Person
    + (void)sayHello {
        
    }
    - (void)sayGood {
        
    }
    @end
    
    • 继承Person的类Student
    #import "Person.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Student : Person
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "Student.h"
    
    @implementation Student
    
    @end
    

    元类

    通过上篇文章,我们知道isa & ISA_MASK可以得到类信息。
    我们实例化一个 Person对象kevin,来摸一下isa研究一下。

    (lldb) p/x kevin
    (Person *) $0 = 0x00000001004b2180
    (lldb) po 0x00000001004b2180
    <Person: 0x1004b2180>
    
    (lldb) x/4gx 0x00000001004b2180
    0x1004b2180: 0x001d80010000848d 0x0000000000000000
    0x1004b2190: 0x0000000000000000 0x0000000000000000
    (lldb) po 0x001d80010000848d
    -dßd�
    
    (lldb) p/x 0x001d80010000848d & 0x00007ffffffffff8ULL
    (unsigned long long) $3 = 0x0000000100008488
    (lldb) po 0x0000000100008488
    Person
    
    (lldb) x/4gx 0x0000000100008488
    0x100008488: 0x0000000100008460 0x00007fff9497d118
    0x100008498: 0x0000000100407610 0x0004802400000007
    (lldb) po 0x0000000100008460
    Person
    
    (lldb) p/x 0x0000000100008460 & 0x00007ffffffffff8ULL
    (unsigned long long) $6 = 0x0000000100008460
    (lldb) po 0x0000000100008460
    Person
    
    (lldb) x/4gx 0x0000000100008460
    0x100008460: 0x00007fff9497d0f0 0x00007fff9497d0f0
    0x100008470: 0x0000000100705bb0 0x0003e03500000007
    (lldb) po 0x00007fff9497d0f0
    NSObject
    
    (lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $9 = 0x00007fff9497d0f0
    (lldb) po 0x00007fff9497d0f0
    NSObject
    
    (lldb) x/4gx 0x00007fff9497d0f0
    0x7fff9497d0f0: 0x00007fff9497d0f0 0x00007fff9497d118
    0x7fff9497d100: 0x000000010050f270 0x0004e03100000007
    (lldb) po 0x00007fff9497d0f0
    NSObject
    

    lldb调试:

    1. p/x kevin获取对象在内存中的首地址
      • 打印首地址po 0x00000001004b2180,我们得到了一个指向Person的指针地址0x1004b2180
    2. x/4gx 0x00000001004b2180获取kevin的内存情况
      • 拿到kevin对象的isa 0x001d80010000848d
    3. 我们将kevin对象的类信息isa(0x001d80010000848d) & 0x00007ffffffffff8ULL
      • 打印类信息的首地址(0x0000000100008488)
      • 对首地址进行po,我们得到Person,说明首地址指向的是内存中的Person,也印证了isa中的shiftcls中存放着类信息。
    4. 我们再读取Person类的内存情况(x/4gx 0x0000000100008488)
      • Person的isa(0x0000000100008460)进行po,发现直接打印出了Person,这里大家不免就有疑惑了,0x00000001000084880x0000000100008460明明指向的不是同一片内存为啥会都打印出Person。继续往下分析...
    5. 我们既然拿到的Person类的isa(0x0000000100008460),我们再对类进行类信息获取(p/x 0x0000000100008460 & 0x00007ffffffffff8ULL)并打印出首地址(0x0000000100008460)
      • 我们发现在第4步得到的Person的类的isa(0x0000000100008460)与我们现在获取到的类信息地址是完全一致的。0x00000001000084880x0000000100008460指向的不是同一内存区域,却打印出了"同一结果"?
      • 这里引出元类0x0000000100008488指向的是Person类,0x0000000100008460指向的是Person的元类元类底层源码自动生成,每个类都会对应一个元类既然有了元类,我们继续摸元类的isa...
    6. 获取Person元类的内存情况x/4gx 0x0000000100008460
      • 打印其首地址po 0x00007fff9497d0f0,我们居然得到了NSObject
      • 继续对NSObjectisa,发现无论是首地址还是其isa都是同一个地址0x00007fff9497d0f0。那这个NSObject是不是就是我们所熟悉的那个NSObject呢?留在后面验证...
        isa走位图.png
        通过上面的分析,是不是就印证了经典的isa走位图。按图上的isa走位我们知道了,最后isa的走位一直指向0x00007fff9497d0f0其实就是根元类。那为什么会打印出NSObject呢?印证与系统的isa是不是同一个
    (lldb) p/x NSObject.class
    (Class) $12 = 0x00007fff9497d118 NSObject
    (lldb) x/4gx 0x00007fff9497d118
    0x7fff9497d118: 0x00007fff9497d0f0 0x0000000000000000
    0x7fff9497d128: 0x00000001004b23c0 0x0001801000000003
    (lldb) po 0x00007fff9497d118
    NSObject
    
    (lldb) po 0x00007fff9497d0f0
    NSObject
    
    (lldb) p/x 0x00007fff9497d0f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $15 = 0x00007fff9497d0f0
    

    分析

    1. 获取NSObject在内存中的首地址p/x NSObject.class得到0x00007fff9497d118
    2. 获取NSObject的内存分布情况x/4gx 0x00007fff9497d118
      • 得到isa(0x00007fff9497d0f0),此时的isa和首地址po出来都是NSObject,结合上面的分析,0x00007fff9497d0f0其实是NSObject的元类,并且内存地址和上面的内存地址是一致的,继续摸isa也是同一个地址。由于NSObject是根类,所以此时的0x00007fff9497d0f0根元类的地址,印证了类在内存中只有一份且再次印证上图。

    继承关系走位图

    继承关系走位图.png
    结合两幅图,就得到了 继承关系及isa走位图.png
    现在我们明白了对象,类,元类,根元类的关系,那么为什么无论是对象,还是类都有isa呢?

    objc_class & objc_object

    • 通过clang
      • 我们发现在底层提供的获取父类,及元类runtime方法,返回值都是由struct objc_class定义的结构体
      • Class也是由struct objc_class定义的。
    __OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
    __OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
    __OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);
    
    typedef struct objc_class *Class;
    
    struct objc_class {
        Class _Nonnull isa __attribute__((deprecated));
    } __attribute__((unavailable));
    

    我们由此,猜想是不是所有的Class都是以objc_class为模板来创建的。
    objc4-781源码中搜索objc_class

    • runtime.h文件中发现
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    

    这里我们可以看到,在OBJC2已经废弃了,从注释我们也可以看出现在用Class代替了struct objc_class *,是不是这里也可以说明Class都是以struct objc_class为模板创建的。继续往下找。

    • objc-runtime-new.h,发现了新版的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();
        }
        ....//省略部分代码
    }
    
    • objc-runtime-old.h,发现了老版的objc_class定义
    struct objc_class : objc_object {
        Class superclass;
        const char *name;
        uint32_t version;
        uint32_t info;
        uint32_t instance_size;
        struct old_ivar_list *ivars;
        struct old_method_list **methodLists;
        Cache cache;
        struct old_protocol_list *protocols;
        // CLS_EXT only
        const uint8_t *ivar_layout;
        struct old_class_ext *ext;
        ...//省略部分代码
    }
    

    这里是不是大家就非常熟悉的看到了objc_class的继承关系,原来无论在老版还是新版objc_class都继承自objc_object

    • 我们再全局搜索一下objc_object
      • 在objc.h文件中
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    objc_object是一个结构体,其中包含了一个isa

    • objc-private.h
    struct objc_object {
    private:
        isa_t isa;
    
    public:
    
        // ISA() assumes this is NOT a tagged pointer object
        Class ISA();
    
        // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
        Class rawISA();
    
        // getIsa() allows this to be a tagged pointer object
        Class getIsa();
    ...
    }
    

    我们看到objc_object其中包含一个isaobjc_class又是继承于objc_object
    结论

    • 结构体类型objc_class继承自objc_object类型,其中objc_object也是一个结构体,且有一个isa属性,所以objc_class也拥有了isa属性
    • 所有以objc_class为模板创建的类都会有isa
    • 所有以objc_object为模板创建的对象都会有isa
      objc_class,objc_object关系图.png

    类结构分析

    我们在上面的探索中得出了类都是以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();
        }
        ....//省略部分代码
    }
    
    • ISA:isa,8字节
    • superclass:指向父类的指针,8字节
    • cache:暂时无法确定大小,接下来一起探索
    • bits:探究的目标,通过内存偏移可以获取到,但需要确定cache的大小
    cache的内存大小

    进入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;
    #if __LP64__
        uint16_t _flags;
    #endif
        uint16_t _occupied;
    

    这里只贴出影响大小的属性,一些staic修饰的属性,不占大小,故不做大小分析

    • 计算第一部分,由if,elif修饰,所以只会存在一份

      • CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        • _buckets 类型是struct bucket_t *,是个结构体指针类型,8字节
        • _mask 类型是mask_t,是uint32_t类型,4个字节
      • CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        • _maskAndBuckets 类型是uintptr_t,是个指针类型,8个字节
        • _mask_unused 类型是mask_t,是uint32_t类型,4个字节
    • _flags 类型是unsigned short,2个字节

    • _occupied 类型是unsigned short,2个字节
      结论
      cache 总共占16个字节

    类bits探索

    • 通过类的首地址偏移32个字节,打印Personbits的内存地址
    (lldb) p/x Person.class
    (Class) $0 = 0x00000001000080e8 Person
    (lldb) p/x 0x00000001000080e8 + 32
    (long) $1 = 0x0000000100008108
    (lldb) p (class_data_bits_t *)0x0000000100008108
    (class_data_bits_t *) $2 = 0x0000000100008108
    

    这里我们知道首地址偏移32位是class_data_bits_t类型的,所有直接p此时的内存地址并强转为class_data_bits_t类型,没有报错,进一步论证此时的内存地址就是存储着bits

    • 获取bitsdata()
    (lldb) p $2->data()
    (class_rw_t *) $3 = 0x00000001011890e0
    

    通过$2->data()去获取class_rw_t,这里可以从两个地方论证

    1. objc_class的结构中,我们可以清晰的看到bits.data()
      objc_class结构.png
    2. 进入class_data_bits_t的内部我们也能发现,class_data_bits内部有data()的get方法,还有set方法
      class_data_bits内部有data()的get方法,还有set方法.png
    • data()是一个class_rw_t类型,再探索一下data(),进入class_rw_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};
            }
        }
    

    methods():方法列表

    • 获取方法列表
      在获取到bits的data()的基础上
    1. 获取data()数据信息
    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = {
          Value = 4295000208
        }
      }
      firstSubclass = nil
      nextSiblingClass = NSUUID
    }
    
    
    1. 获取方法列表
    (lldb) p $3.methods()
    (const method_array_t) $4 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000080d8
          arrayAndFlag = 4295000280
        }
      }
    }
    (lldb) p $4.list
    (method_list_t *const) $5 = 0x00000001000080d8
    (lldb) p *$5
    (method_list_t) $6 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "sayHello"
          types = 0x0000000100003f81 "v16@0:8"
          imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
        }
      }
    }
    
    1. 对方法列表进行单个输出
    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "sayHello"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e10 (KCObjc`-[Person sayHello])
    }
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = "bobby"
      types = 0x0000000100003f95 "@16@0:8"
      imp = 0x0000000100003e20 (KCObjc`-[Person bobby])
    }
    (lldb) p $6.get(2)
    (method_t) $9 = {
      name = "setBobby:"
      types = 0x0000000100003f9d "v24@0:8@16"
      imp = 0x0000000100003e40 (KCObjc`-[Person setBobby:])
    }
    (lldb) p $6.get(3)
    (method_t) $10 = {
      name = ".cxx_destruct"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e70 (KCObjc`-[Person .cxx_destruct])
    }
    (lldb) p $6.get(4)
    Assertion failed: (i < count), function get, 
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    

    这里我们看到有4个方法,.cxx_destructsayHello,还有bobbygetset方法,为啥没有我们的类方法sayNB呢?

    properties():属性列表

    探索方法,同上,这里我们也会发现,没有成员边量,只有我们的属性。

    成员变量的存储

    刚刚我们在properties()中未发现成员变量bobby,那个成员变量存在哪里呢?
    通过探索,我们在class_rw_t的结构中发现了一个ro()ro()的类型是``,进入其内部

    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;
    

    取到ro()中的ivars,然后就可以看到我们的成员变量,和属性都在里面

    (lldb) p $16.ivars
    (const ivar_list_t *const) $19 = 0x0000000100008140
    (lldb) p *$19
    (const ivar_list_t) $20 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x00000001000081a8
          name = 0x0000000100003f4e "name"
          type = 0x0000000100003f89 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    

    类方法的存储

    我们在Person类的方法列表中,没有找到类方法。我们在元类的方法列表中发现了Person的类方法,由此我们可知,类的实例方法是存在类的bits里面。类的类方法是存放在元类的bits里面

    相关文章

      网友评论

          本文标题:类,类结构分析

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