美文网首页iOS收藏
OC 类探索(一)

OC 类探索(一)

作者: HotPotCat | 来源:发表于2021-06-18 14:01 被阅读0次

    一、isa->类和元类

    上篇文章分析了对象的isa底层实现以及是如何与cls关联的,这边文章继续分析类的结构。

    HPObject *obj = [HPObject alloc];
    

    obj打断点查看:

    image.png
    • x/4gx获取objisa指针。
    • isa & mask获取类对象HPObject
    • po验证获取到的是HPObject

    这个时候如果对类对象继续x/4gx呢?

    image.png
    发现HPObjectisa &mask后也是HPObject,但是两者的地址不一样。并且类对象MASK前后地址没有变化说明类对象的isa是一个单纯的指针,没有位域信息。

    1.1 类对象内存个数

    既然上面验证出来类对象也是会开辟空间的,那么类对象在内存中有多少份呢?
    验证代码:

    void verifyClassNumber() {
        Class class1 = [HPObject class];
        Class class2 = [HPObject alloc].class;
        Class class3 = object_getClass([HPObject alloc]);
        Class class4 = objc_getClass("HPObject");
        //再次创建对象
        Class class5 = [HPObject alloc].class;
        NSLog(@"\n%p\n%p\n%p\n%p\n%p",class1,class2,class3,class4,class5);
    }
    

    结果:

    0x1000082f0
    0x1000082f0
    0x1000082f0
    0x1000082f0
    0x1000082f0
    

    可以看到都指向一个内存地址,所以只存在一份。这几种获取类对象的方式区别如下:


    获取类对象对比

    上面通过HPObjectisa &mask后也是HPObject,但是两者的地址不一样。说明类对象的isa指针获取的不是类对象。那么它是什么?(元类)。
    这个时候可以用MachOView查看下符号表:

    image.png
    看到有一个METACLASS_HPObject,但是元类并不是我们创建的。那么意味着是系统生成和编译的。
    所以就有对应关系:实例对象(isa)->类对象(isa)->元类

    二、isa走位图和继承链

    2.1 isa走位图

    上面得到了实例对象(isa)->类对象(isa)->元类,这个时候又有一个疑问,元类的isa指向哪里呢?

    image.png
    • 实例对象(isa)->类对象(sia)->元类(isa)->根元类(NSObject isa)->自身
    • 根类实例对象(isa)->根元类(isa)->自身

    代码验证下:

    void verifyIsaLinked() {
        //NSObject 实例对象
        NSObject *obj = [NSObject alloc];
        //NSObject类
        Class class = object_getClass(obj);
        //NSObject元类
        Class metaClass = object_getClass(class);
        //NSObject根元类
        Class rootMetaClass = object_getClass(metaClass);
        //NSObject根根元类
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p",obj,class,metaClass,rootMetaClass,rootRootMetaClass);
    }
    

    结果:

    实例对象:0x10136bf30 
    类:0x100358140 
    元类:0x1003580f0 
    根元类:0x1003580f0 
    根根元类:0x1003580f0
    

    这样就得到了isa走位图:

    isa走位图

    2.2 类和元类的继承链

    上面分析到了元类,那么元类有父类么?

    HPObject元类的父类

    Class hpMetaClass = object_getClass(HPObject.class);
    //HPObject元类的父类
    Class hpSuperMetaClass = class_getSuperclass(hpMetaClass);
    NSLog(@"\nHPObject元类的父类:%@ - %p",hpSuperMetaClass,hpSuperMetaClass);
    //NSObject元类
    Class objMetaClass = object_getClass(NSObject.class);
    NSLog(@"\nNSObject元类:%@ - %p",objMetaClass,objMetaClass);
    

    结果:

    HPObject元类的父类:NSObject - 0x1003580f0
    NSObject元类:NSObject - 0x1003580f0
    

    可以得出结论:HPObject元类的父类是NSObject的元类

    HPObject子类(HPSubobject)元类的父类
    新建一个HPObject的子类HPSubobject同样获取它的元类的父类:

    ///HPObject元类
    Class hpMetaClass = object_getClass(HPObject.class);
    NSLog(@"\nHPObject元类:%@ - %p",hpMetaClass,hpMetaClass);
    //HPSubobject元类
    Class hpsMetaClass = object_getClass(HPSubobject.class);
    //HPSubobject元类的父类
    Class hpsSuperMetaClass = class_getSuperclass(hpsMetaClass);
    NSLog(@"\nHPSubobject元类的父类:%@ - %p",hpsSuperMetaClass,hpsSuperMetaClass);
    

    结果:

    HPObject元类:HPObject - 0x1000083b0
    HPSubobject元类的父类:HPObject - 0x1000083b0
    

    所以 元类也有继承链

    NSObject(根元类)的父类
    那么NSObject的元类也就是根元类的父类呢?

    //NSObject 实例对象
    NSObject *obj = [NSObject alloc];
    //NSObject类
    Class class = object_getClass(obj);
    //NSObject元类
    Class metaClass = object_getClass(class);
    //NSObject元类的父类
    Class superMetaClass = class_getSuperclass(metaClass);
    NSLog(@"\n类:%@ - %p \n元类的父类:%@ - %p",class,class,superMetaClass,superMetaClass);
    

    结果:

    类:NSObject - 0x100358140 
    元类的父类:NSObject - 0x100358140
    

    可以看到是根元类的父类是NSObject,万物基于NSObject。至此元类的继承链就清晰了。

    类的继承关系
    已知HPSubobject->HPObject->NSObject,需要验证NSObject的父类:

    Class objSuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"\n%@ - %p",objSuperClass,objSuperClass);
    

    结果:

    (null) - 0x0
    

    所以NSObject不存在父类

    这样就得到了完整的类和元类的继承链:


    类的继承链

    通过isa的走位链和类的继承关系就得到了那张苹果官网著名的图:

    isa流程图.jpg

    三、源码分析类结构

    3.1类的内存结构

    既然类也有isa,那么类的结构是怎样的呢?类在底层是objc_class类型,在runtime.h(OBJC2_UNAVAILABLE)与objc-runtime-new.h中都有声明。现在使用的都是objc-runtime-new.h中的objc_class。它是一个结构体要研究它的结构需要看它的成员变量。
    类的结构如下:

    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    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
    };
    
    • ISA:继承自objc_object,指向元类。
    • superclass:指向父类。
    • cache:方法缓存,当调用一次方法后就会缓存进vtable中,加速下次调用。
    • bits:具体类信息(成员变量、属性、方法)。

    在源码中能找到class_rw_t中封装了获取methodspropertiesprotocols的方法。而class_rw_t是存在bits中的。那么怎么通过objc_class获取bits数据呢?

    3.2 类的结构内存计算

    既然类的底层数据是结构体,那么只要找到首地址通过偏移就能得到bits数据的地址。ISAsuperclass都是结构体指针分别占用8字节,那么cache占多大空间呢?

    cache_t中内容很多包括很多函数和static的常量(不占结构体空间),其实只需要关注成员变量即可。
    cache_t结构如下:

    struct cache_t {
    private:
        explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //unsigned long 8字节
        union {
            struct {
                explicit_atomic<mask_t>    _maybeMask;//uint32_t 4字节
    #if __LP64__
                uint16_t                   _flags;//2字节
    #endif
                uint16_t                   _occupied;//2字节
            };
            explicit_atomic<preopt_cache_t *> _originalPreoptCache;//指针 8字节
        };
    };
    

    cache_t包含两部分_bucketsAndMaybeMaskunsigned long 8字节)与联合体,联合体中有一个_originalPreoptCache(指针 8字节)与结构体(_originalPreoptCache与结构体只需要计算一个,共用一块内存)。所以cache_t大小为16字节。

    那么只需要类的首地址偏移32字节(0x20 = ISA(8) + superclass (8) + cache (16))就能得到bits的地址。

    指针的步长与指针类型有关。

    3.3 lldb分析类的结构

    3.3.1 ISA

    image.png
    • 类的isa就是一个纯指针,指向元类。

    3.3.2 superclass

    image.png
    • 类的superclass就是类的父类。

    3.3.3 cache

    cache方法缓存相关的内容cache

    3.3.4 bits

    bits的类型是class_data_bits_t结构如下:

    struct class_data_bits_t {
        uintptr_t bits;
    };
    

    在源码中有class_rw_t * plus custom rr/alloc flags注释,也就是说class_data_bits_t的核心是class_rw_t。查找源码发现data()返回的是class_rw_t*类型。

    data()

    data()实现如下:

    #if __LP64__
    #define FAST_DATA_MASK        0x00007ffffffffff8UL
    #else
    #define FAST_DATA_MASK        0xfffffffcUL
    #endif
    
    
    class_rw_t* data() {
       return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    
    • 64位下data()占用44[3~46]32位下data()占用30[2~31]
    • 所以也就是bits(class_data_bits_t)的[3~46]/[2~31]位是data()(class_rw_t)。

    同理源码中有以下代码:

    #if __LP64__
    
    // class is a Swift class from the pre-stable Swift ABI
    #define FAST_IS_SWIFT_LEGACY    (1UL<<0)
    // class is a Swift class from the stable Swift ABI
    #define FAST_IS_SWIFT_STABLE    (1UL<<1)
    // class or superclass has default retain/release/autorelease/retainCount/
    //   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
    #define FAST_HAS_DEFAULT_RR     (1UL<<2)
    // data pointer
    #define FAST_DATA_MASK          0x00007ffffffffff8UL
    
    #else
    
    // class is a Swift class from the pre-stable Swift ABI
    #define FAST_IS_SWIFT_LEGACY  (1UL<<0)
    // class is a Swift class from the stable Swift ABI
    #define FAST_IS_SWIFT_STABLE  (1UL<<1)
    #define FAST_DATA_MASK        0xfffffffcUL
    
    #endif // __LP64__
    
    
    bool isSwiftStable() {
        return getBit(FAST_IS_SWIFT_STABLE);
    }
    
    bool isSwiftLegacy() {
        return getBit(FAST_IS_SWIFT_LEGACY);
    }
    
    bool hasCustomRR() const {
        return !bits.getBit(FAST_HAS_DEFAULT_RR);
    }
    
    • FAST_IS_SWIFT_LEGACY:第0位类是否来自稳定的Swift ABISwift 类。(遗留的类)
    • FAST_IS_SWIFT_STABLE:第1位类是否来自稳定的Swift ABISwift 类。
    • FAST_HAS_DEFAULT_RR:第2位判断当前类或者父类是否含有默认的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法。(仅64位)

    前面已经得出结论类的首地址偏移32字节就能得到bits的地址:

    image.png
    • 首地址 + 偏移 得到 bits的地址$8
    • 强转bits地址($8)为class_data_bits_t指针($9)。
    • bits指针($9)调用data()函数获取class_rw_t指针($10)。
    • 打印class_rw_t指针所指向的值(*$10)。

    class_rw_t

    既然data()class_rw_t结构,它的内存结构如下:

    struct class_rw_t {
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
    
        explicit_atomic<uintptr_t> ro_or_rw_ext;
    
        Class firstSubclass;
        Class nextSiblingClass;
    };
    
    • flags
    • witness
    • index32位下有效,记录类在数组中的索引。
    • ro_or_rw_ext:存储属性,方法,协议,成员变量。
    • firstSubclass:第一个子类。
      在上面的调试中firstSubclassnil是因为它没有被使用。是懒加载类,如果使用了或者实现了+ load方法则会指向子类。
    • nextSiblingClass:相邻类。

    分析到这里显然核心就是ro_or_rw_ext了,查看class_rw_t源码发现提供了methods、properties、protocols方法。

    修改HPObject以及添加方法,文件如下:

    image.png

    HPObject:

    @interface HPObject : NSObject {
        int height;
        NSString *sex;
    }
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    
    - (void)instanceMethod;
    + (void)classMethod;
    
    @end
    

    HPObject+Additions1:

    @interface HPObject (Additions1)
    
    - (void)additions1InstanceMethod;
    
    + (void)additions1ClassMethod;
    
    @end
    

    HPObject+additions2:

    @interface HPObject (additions2)
    
    - (void)additions2InstanceMethod;
    
    + (void)additions2ClassMethod;
    
    @end
    

    ⚠️:方法要有对应的实现。

    properties()

    (lldb) x/6gx HPObject.class
    0x1000083d8: 0x00000001000083b0 0x0000000100358140
    0x1000083e8: 0x000000010034f360 0x0000803400000000
    0x1000083f8: 0x00000001032babd4 0x00000001000ac920
    (lldb) p (class_data_bits_t *)0x1000083f8
    (class_data_bits_t *) $1 = 0x00000001000083f8
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x00000001032babd0
    (lldb) p $2->properties()
    (const property_array_t) $3 = {
      list_array_tt<property_t, property_list_t, RawPtr> = {
         = {
          list = {
            ptr = 0x0000000100008308
          }
          arrayAndFlag = 4295000840
        }
      }
    }
    

    通过properties获取到的属性是一个property_array_t,结构如下:

    class property_array_t : 
        public list_array_tt<property_t, property_list_t, RawPtr>
    {
        typedef list_array_tt<property_t, property_list_t, RawPtr> Super;
    
     public:
        property_array_t() : Super() { }
        property_array_t(property_list_t *l) : Super(l) { }
    };
    

    property_array_t继承自list_array_tt是一个两层结构property_list_t < property_t>list_array_tt中有iterator意味着它有遍历能力。
    通过list能获取到RawPtr<property_list_t>

    (lldb) p $3.list
    (const RawPtr<property_list_t>) $4 = {
      ptr = 0x0000000100008308
    }
    

    通过访问ptr能够获取到property_list_t数组:

    (lldb) p $4.ptr
    (property_list_t *const) $5 = 0x0000000100008308
    (lldb) p *$5
    (property_list_t) $6 = {
      entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
    }
    

    $5就相当于迭代器了,property_list_t源码结构如下:

    struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
    };
    

    property_list_t是一个空实现继承自entsize_list_ttentsize_list_tt中有一个get方法:

    struct entsize_list_tt {
        uint32_t entsizeAndFlags;
        uint32_t count;
        Element& get(uint32_t i) const { 
            ASSERT(i < count);
            return getOrEnd(i);
        }
    };
    

    所以可以根据get方法获取元素:

    (lldb) p $6.get(0)
    (property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
    (lldb) p $6.get(1)
    (property_t) $8 = (name = "age", attributes = "Ti,N,V_age")
    (lldb) p $6.get(2)
    Assertion failed: (i < count), function get, file /Volumes/HOTPOTCAT/sourcecode/objc4/objc4-818.2/runtime/objc-runtime-new.h, line 625.
    error: Execution was interrupted, reason: signal SIGABRT.
    The process has been returned to the state before expression evaluation.
    

    这个时候并没有成员变量heightsex。只有nameage两个属性。

    methods()

    properties相同,通过class_rw_tmethods方法获取方法:

    (lldb) p $2->methods()
    (const method_array_t) $3 = {
      list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
         = {
          list = {
            ptr = 0x0000000100008098
          }
          arrayAndFlag = 4295000216
        }
      }
    }
    (lldb) p $3.list.ptr
    (method_list_t *const) $4 = 0x0000000100008098
    (lldb) p *$4
    (method_list_t) $5 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 8)
    }
    (lldb) p $5.get(0)
    (method_t) $6 = {}
    
    • 看到有8个方法。
    • 由于method_list_t也是继承自entsize_list_tt,所以直接通过get()获取,结果返回空。

    那么分别看下property_tmethod_t的实现。
    property_t:

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

    method_t:

    struct method_t {
    //......
        struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
        big &big() const {
            ASSERT(!isSmall());
            return *(struct big *)this;
        }
    //......
    };
    

    两者的区别是property_t有成员变量,method_t没有成员变量。索引method_t打印为空。但是method_t中有一个结构体big中有nameimp,并且提供了一个big方法,所以可以通过big方法获取:

    (lldb) p $5.get(0).big()
    (method_t::big) $7 = {
      name = "additions1InstanceMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003c30 (HPObjcTest`-[HPObject(Additions1) additions1InstanceMethod])
    }
    (lldb) p $5.get(1).big()
    (method_t::big) $8 = {
      name = "instanceMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003c50 (HPObjcTest`-[HPObject instanceMethod])
    }
    (lldb) p $5.get(2).big()
    (method_t::big) $9 = {
      name = "additions2InstanceMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003d50 (HPObjcTest`-[HPObject(additions2) additions2InstanceMethod])
    }
    (lldb) p $5.get(3).big()
    (method_t::big) $10 = {
      name = ".cxx_destruct"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003d00 (HPObjcTest`-[HPObject .cxx_destruct])
    }
    (lldb) p $5.get(4).big()
    (method_t::big) $11 = {
      name = "name"
      types = 0x0000000100003f55 "@16@0:8"
      imp = 0x0000000100003c60 (HPObjcTest`-[HPObject name])
    }
    (lldb) p $5.get(5).big()
    (method_t::big) $12 = {
      name = "setName:"
      types = 0x0000000100003f5d "v24@0:8@16"
      imp = 0x0000000100003c90 (HPObjcTest`-[HPObject setName:])
    }
    (lldb) p $5.get(6).big()
    (method_t::big) $13 = {
      name = "age"
      types = 0x0000000100003f68 "i16@0:8"
      imp = 0x0000000100003cc0 (HPObjcTest`-[HPObject age])
    }
    (lldb) p $5.get(7).big()
    (method_t::big) $14 = {
      name = "setAge:"
      types = 0x0000000100003f70 "v20@0:8i16"
      imp = 0x0000000100003ce0 (HPObjcTest`-[HPObject setAge:])
    }
    
    • 除了两个属性的4setter + getter方法外还有类和分类的实例方法以及. cxx_destruct

    protocols()

    (lldb)  x/6gx HPObject.class
    0x1000088a0: 0x0000000100008878 0x000000010036b140
    0x1000088b0: 0x0000000100362360 0x0000803c00000000
    0x1000088c0: 0x0000000100637d64 0x00000001000b9980
    (lldb) p (class_data_bits_t *)0x1000088c0
    (class_data_bits_t *) $16 = 0x00000001000088c0
    (lldb) p $16->data()
    (class_rw_t *) $17 = 0x0000000100637d60
    (lldb) p $17->protocols()
    (const protocol_array_t) $18 = {
      list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
         = {
          list = {
            ptr = 0x0000000100008678
          }
          arrayAndFlag = 4295001720
        }
      }
    }
    

    protocols()获取的是protocol_array_t:

    class protocol_array_t : 
        public list_array_tt<protocol_ref_t, protocol_list_t, RawPtr>
    {
        typedef list_array_tt<protocol_ref_t, protocol_list_t, RawPtr> Super;
    
     public:
        protocol_array_t() : Super() { }
        protocol_array_t(protocol_list_t *l) : Super(l) { }
    };
    

    继承自list_array_tt与其属性和方法一致。通过list.ptr能获取到protocol_list_t

    (lldb) p $18.list.ptr
    (protocol_list_t *const) $19 = 0x0000000100008678
    (lldb) p *$19
    (protocol_list_t) $20 = (count = 1, list = protocol_ref_t [] @ 0x00007fb14e4f68b8)
    

    结构如下:

    struct protocol_list_t {
        // count is pointer-sized by accident.
        uintptr_t count;
        protocol_ref_t list[0]; // variable-size
    };
    

    它没有继承自entsize_list_ttprotocol_ref_t是个无符号长整形:

    typedef uintptr_t protocol_ref_t;  // protocol_t *, but unremapped
    

    尝试用get函数获取元素:

    (lldb) p $20.get(0).big()
    error: <user expression 22>:1:5: no member named 'get' in 'protocol_list_t'
    $20.get(0).big()
    ~~~ ^
    (lldb) p $20.get(0)
    error: <user expression 23>:1:5: no member named 'get' in 'protocol_list_t'
    $20.get(0)
    ~~~ ^
    

    果然都失败了,可以看到protocol_ref_t既然不是个结构体所以没有名字相关的数据。没有继承自entsize_list_tt所以没有get函数。protocol_ref_t的注视看着与protocol_t有关,
    查看下protocol_t的结构:

    struct protocol_t : objc_object {
        const char *mangledName;
        struct protocol_list_t *protocols;
        method_list_t *instanceMethods;
        method_list_t *classMethods;
        method_list_t *optionalInstanceMethods;
        method_list_t *optionalClassMethods;
        property_list_t *instanceProperties;
        uint32_t size;   // sizeof(protocol_t)
        uint32_t flags;
        // Fields below this point are not always present on disk.
        const char **_extendedMethodTypes;
        const char *_demangledName;
        property_list_t *_classProperties;
    };
    

    现在的问题就是protocol_ref_t怎么转变成protocol_t
    搜索后发现在remapProtocolprotocol_ref_t直接强转成了protocol_t:

    image.png
    (lldb) p $20.list[0]
    (protocol_ref_t) $21 = 4295002416
    (lldb) p (protocol_t *)$21
    (protocol_t *) $22 = 0x0000000100008930
    (lldb) p *$22
    (protocol_t) $23 = {
      objc_object = {
        isa = {
          bits = 4298551496
          cls = Protocol
           = {
            nonpointer = 0
            has_assoc = 0
            has_cxx_dtor = 0
            shiftcls = 537318937
            magic = 0
            weakly_referenced = 0
            unused = 0
            has_sidetable_rc = 0
            extra_rc = 0
          }
        }
      }
      mangledName = 0x0000000100003c13 "HPObjectProtocol"
      protocols = 0x00000001000085c8
      instanceMethods = 0x00000001000085e0
      classMethods = 0x0000000100008600
      optionalInstanceMethods = 0x0000000000000000
      optionalClassMethods = 0x0000000000000000
      instanceProperties = 0x0000000000000000
      size = 96
      flags = 0
      _extendedMethodTypes = 0x0000000100008620
      _demangledName = 0x0000000000000000
      _classProperties = 0x0000000000000000
    }
    

    这样就获取到了protocol_t中的数据。

    protocol_t数据结构获取:bits->data()->protocols().list.ptr.list[0]->(protocol_t *)强转

    instanceMethods

    (lldb) p $23.instanceMethods
    (method_list_t *) $25 = 0x00000001000085e0
    (lldb) p *$25
    (method_list_t) $26 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 1)
    }
    (lldb) p $26.get(0).big()
    (method_t::big) $27 = {
      name = "protocolInstanceMethod"
      types = 0x0000000100003eb5 "v16@0:8"
      imp = 0x0000000000000000
    }
    
    • 实例方法的获取与methods相同。

    protocols
    可以看到protocol_t中有protocols那么它是什么数据呢?

    (lldb) p $23.protocols
    (protocol_list_t *) $24 = 0x00000001000085c8
    (lldb) p *$24
    (protocol_list_t) $28 = (count = 1, list = protocol_ref_t [] @ 0x00007fb151e12bf8)
    (lldb) p  $28.list[0]
    (protocol_ref_t) $31 = 4295002320
    (lldb) p (protocol_t *)$31
    (protocol_t *) $32 = 0x00000001000088d0
    (lldb) p *$32
    (protocol_t) $33 = {
      objc_object = {
        isa = {
          bits = 0
          cls = nil
           = {
            nonpointer = 0
            has_assoc = 0
            has_cxx_dtor = 0
            shiftcls = 0
            magic = 0
            weakly_referenced = 0
            unused = 0
            has_sidetable_rc = 0
            extra_rc = 0
          }
        }
      }
      mangledName = 0x0000000100003c0a "NSObject"
      protocols = 0x0000000000000000
      instanceMethods = 0x00000001000082f0
      classMethods = 0x0000000000000000
      optionalInstanceMethods = 0x00000001000084c0
      optionalClassMethods = 0x0000000000000000
      instanceProperties = 0x00000001000084e0
      size = 96
      flags = 0
      _extendedMethodTypes = 0x0000000100008528
      _demangledName = 0x0000000000000000
      _classProperties = 0x0000000000000000
    }
    

    可以看到是属于NSObject的。这个时候protocols就没有指向了。instanceMethods还仍然有数据:

    (lldb) p $33.instanceMethods
    (method_list_t *) $34 = 0x00000001000082f0
    (lldb) p *$34
    (method_list_t) $35 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 19)
    }
    (lldb) p $35.get(0).big()
    (method_t::big) $36 = {
      name = "isEqual:"
      types = 0x0000000100003ebd "c24@0:8@16"
      imp = 0x0000000000000000
    }
    (lldb) p $35.get(1).big()
    (method_t::big) $37 = {
      name = "class"
      types = 0x0000000100003ec8 "#16@0:8"
      imp = 0x0000000000000000
    }
    (lldb) p $35.get(2).big()
    (method_t::big) $38 = {
      name = "self"
      types = 0x0000000100003ed0 "@16@0:8"
      imp = 0x0000000000000000
    }
    

    可以看到有19个方法,都有哪些呢?如下图:

    image.png
    没有@optionaldebugDescription。这也正常因为还有optionalInstanceMethodsoptionalClassMethods

    classMethods

    (lldb) p $57.get(0).big()
    (method_t::big) $58 = {
      name = "protocolClassMethod"
      types = 0x0000000100003eb5 "v16@0:8"
      imp = 0x0000000000000000
    }
    

    ⚠️只要遵循协议就能关联到,不需要实现方法。本质上与对象通过isa关联cls相同。协议在底层也继承自objc_object。也有isa的数据结构。也可以添加属性。

    ro()

    properties中并没有找到实例变量,但是在class_rw_tmethods()附近发现了ro(),它返回class_ro_t类型:

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
    
        explicit_atomic<const char *> name;
        // With ptrauth, this is signed if it points to a small list, but
        // may be unsigned if it points to a big list.
        void *baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    

    发现class_rw_t中有ivar

    (lldb) p $2->ro()
    (const class_ro_t *) $3 = 0x0000000100008238
    (lldb) p *$3
    (const class_ro_t) $4 = {
      flags = 388
      instanceStart = 8
      instanceSize = 40
      reserved = 0
       = {
        ivarLayout = 0x0000000100003e7a "\x11\x11"
        nonMetaclass = 0x0000000100003e7a
      }
      name = {
        std::__1::atomic<const char *> = "HPObject" {
          Value = 0x0000000100003e71 "HPObject"
        }
      }
      baseMethodList = 0x0000000100008098
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100008280
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000100008308
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    (lldb) p $4.ivars
    (const ivar_list_t *const) $5 = 0x0000000100008280
    (lldb) p *$5
    (const ivar_list_t) $6 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 4)
    }
    
    • ivars结构为ivar_list_t类型,也是继承自entsize_list_tt,所以同样应该能够根据get()去获取ivar_t
    • 4个成员变量。

    ivar_t结构如下:

    struct ivar_t {
        int32_t *offset;
        const char *name;
        const char *type;
        // alignment is sometimes -1; use alignment() instead
        uint32_t alignment_raw;
        uint32_t size;
    
        uint32_t alignment() const {
            if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
            return 1 << alignment_raw;
        }
    };
    

    获取实例变量:

    (lldb) p $6.get(0)
    (ivar_t) $7 = {
      offset = 0x0000000100008340
      name = 0x0000000100003ec3 "height"
      type = 0x0000000100003f47 "i"
      alignment_raw = 2
      size = 4
    }
    (lldb) p $6.get(1)
    (ivar_t) $8 = {
      offset = 0x0000000100008348
      name = 0x0000000100003eca "sex"
      type = 0x0000000100003f49 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    (lldb) p $6.get(2)
    (ivar_t) $9 = {
      offset = 0x0000000100008350
      name = 0x0000000100003ece "_age"
      type = 0x0000000100003f47 "i"
      alignment_raw = 2
      size = 4
    }
    (lldb) p $6.get(3)
    (ivar_t) $10 = {
      offset = 0x0000000100008358
      name = 0x0000000100003ed3 "_name"
      type = 0x0000000100003f49 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    

    类方法

    methods()中并没有找到类方法,那么类方法存储在哪里呢?根据isa的走位直接进去元类查看。元类的结构也是objc_class所以可以尝试同样的方式获取。

    (lldb) x/4gx HPObject.class
    0x1000083d8: 0x00000001000083b0 0x0000000100358140
    0x1000083e8: 0x000000010034f360 0x0000803400000000
    (lldb) x/6gx 0x00000001000083b0
    0x1000083b0: 0x00000001003580f0 0x00000001003580f0
    0x1000083c0: 0x0000000102930780 0x0002e03500000003
    0x1000083d0: 0x00000001029303e4 0x00000001000083b0
    (lldb) p (class_data_bits_t *)0x1000083d0
    (class_data_bits_t *) $1 = 0x00000001000083d0
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x00000001029303e0
    (lldb) p $2->methods()
    (const method_array_t) $3 = {
      list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
         = {
          list = {
            ptr = 0x0000000100008048
          }
          arrayAndFlag = 4295000136
        }
      }
    }
    (lldb) p $3.list.ptr
    (method_list_t *const) $4 = 0x0000000100008048
    (lldb) p *$4
    (method_list_t) $5 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 3)
    }
    (lldb) p $5.get(0).big()
    (method_t::big) $6 = {
      name = "additions1ClassMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003c20 (HPObjcTest`+[HPObject(Additions1) additions1ClassMethod])
    }
    (lldb) p $5.get(1).big()
    (method_t::big) $7 = {
      name = "classMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003c40 (HPObjcTest`+[HPObject classMethod])
    }
    (lldb) p $5.get(2).big()
    (method_t::big) $8 = {
      name = "additions2ClassMethod"
      types = 0x0000000100003f3f "v16@0:8"
      imp = 0x0000000100003d40 (HPObjcTest`+[HPObject(additions2) additions2ClassMethod])
    }
    
    • 通过类的isa找到元类。
    • 元类的结构也是objc_class,在元类中以同样的方式获取bitsdata()methods()就能获取到类方法了。
    • 元类的作用就是存储类方法。(在底层没有所谓的类方法,都是对象方法。)

    结论:类和分类的类方法存储在元类中。

    至此,属性、方法和实例变量的结构就清晰了,结构如下图:


    objc_class结构

    总结

    • 属性获取:类->bits(offset 0x20)->data()->properties().list.ptr.get(index)
    • 实例方法获取:类->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()
    • 成员变量获取:类->bits(offset 0x20)->ro().ivars.get(index)
    • 类方法获取: 类->isa->bits(offset 0x20)->data()->methods().list.ptr.get(index).big()

    四、 __has_feature(ptrauth_calls)

    在源码分析中经常看到__has_feature(ptrauth_calls),那么它究竟有什么作用呢?

    • __has_feature:判断编译器是否支持某个功能
    • ptrauth_calls:指针身份验证,针对arm64e架构;使用Apple A12或更高版本A系列处理器的设备(iPhoneX以后,不包括X)支持arm64e架构。
      也就是iPhone X以上的设备支持指针验证。

    Devices using the Apple A12 or later A-series processor — like the iPhone XS, iPhone XS Max, and iPhone XR — support the arm64e architecture. To test your adoption, you have to run your app on one of these devices. You can’t test using the Simulator.
    参考链接
    可以在Build Settings -> Architectures中配置:

    image.png

    PAC

    这里就引出了一个问题,什么是PAC
    PACPointer Authentication,它的目的即检测和保护地址不被意外或恶意修改,使得应用执行更加安全。PAC特性是由硬件提供的,保护了函数调用期间,栈空间和地址的安全。
    具体可以参考:arm64e与PAC

    相关文章

      网友评论

        本文标题:OC 类探索(一)

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