美文网首页
iOS底层原理探究 - 类结构分析

iOS底层原理探究 - 类结构分析

作者: JasonL | 来源:发表于2021-06-29 00:36 被阅读0次

    前言

    等风来不如追风去,总有那么一个人在这风景正好的季节来到你的身边。在上一篇探究了对象的本质和isa指针的底层,那么我们继续来看的底层结构。

    补充知识

    • 在OC环境下使用的类,在底层都有替换的类去实现
    底层实现.png

    isa 和类的关联

    类的isa

    探索对象的时候我们已经知道,对象的结构体中的isa指向的是,这个时候就会想,也是一个对象,中也有isa,那么isa又指向哪里呢?如下图:

    类和isa的关联.png 从图中可以看出,JCPerson类对应了两个内存地址0x00000001000085f00x00000001000085c8,哪一个才是JCPerson的地址呢?一个在内存中存在几个内存地址呢?接下来看下图: 类的地址.png
    看到这里应该非常清晰了,一个只有一个内存地址,0x00000001000085f0JCPerson的类地址,而0x00000001000085c8这个类地址苹果把它叫做元类
    总结:
    • 元类由系统编译器自动生成和编译,与创建者无关
    • 对象isa指向类对象isa指向元类

    isa的走位图

    上面我们已经分析了对象isa的走向,那么元类isa又指向哪里呢?结合LLDB来进行探索。

    LLDB调试isa走位图.png 从图中可以得到:
    • 对象 isa --> 类 isa --> 元类 isa --> 根元类isa --> 根元类(自己)
    • 根类(NSObject) isa --> 根元类 isa --> 根元类(自己)
    isa的流程图:
    isa走位图.png

    类、元类、根元类继承图

    创建JCPerson,JCTeacher,NSObject的相关代码,探究一下它们的继承关系。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"%@",class_getSuperclass(JCTeacher.class));
            NSLog(@"%@",class_getSuperclass(JCPerson.class));
            NSLog(@"%@",class_getSuperclass(NSObject.class));
            # NSObject实例对象
            NSObject *object1 = [NSObject alloc];
            # NSObject类
            Class class = object_getClass(object1);
            # NSObject元类
            Class metaClass = object_getClass(class);
            # NSObject根元类
            Class rootMetaClass = object_getClass(metaClass);
            # NSObject根根元类
            Class rootRootMetaClass = object_getClass(rootMetaClass);
            NSLog(@"\nNSObject实例对象  %p\nNSObject类  %p\nNSObject元类  %p\nNSObject根元类  %p\nNSObject根根元类  %p",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
            # NSObject 根类特殊情况
            Class nsuperClass = class_getSuperclass(NSObject.class);
            NSLog(@"%@ - %p",nsuperClass,nsuperClass);
            # 根元类 -> NSObject
            Class rnsuperClass = class_getSuperclass(metaClass);
            NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
            # JCPerson元类和元类的父类
            Class pMetaClass = object_getClass(JCPerson.class);
            Class psuperClass = class_getSuperclass(pMetaClass);
            NSLog(@"%@ - %p",psuperClass,psuperClass);
            # JCTeacher元类和元类的父类
            Class tMetaClass = object_getClass(JCTeacher.class);
            Class tsuperClass = class_getSuperclass(tMetaClass);
            NSLog(@"%@ - %p",tsuperClass,tsuperClass);
        }
        return 0;
    }
    
    类、元类的继承关系.png
    源码分析中知道,NSObject的父类打印结果是nilNSObject根元类的父类的地址等于NSObject类的地址;JCPerson元类的父类的地址等于NSObject的元类。
    • JCTeacher-->JCPerson-->NSObject-->nil
    • JCTeacher元类-->JCPerson元类-->NSObject元类-->NSObject根元类-->NSObject
    的继承图:
    类的继承图.png
    isa流程图和继承链:
    isa流程图和继承图.png

    内存偏移

    在前面探究对象的底层实现,我们了解到对象属性getter方法底层实现是通过首地址+内存偏移的方式去获取内存中的变量。接下来我们来看一下内存偏移

    基本指针
    # 普通指针
    int a = 10; //
    int b = 10; //
    JCNSLog(@"%d -- %p",a,&a);
    JCNSLog(@"%d -- %p",b,&b);
    
    ==========: 10 -- 0x7ffeefbff4ec
    ==========: 10 -- 0x7ffeefbff4e8
    
    • a的地址是0x7ffeefbff4ecb的地址是0x7ffeefbff4e8,相差4个字节,int类型是4个字节的长度
    • a>b的地址,从高地址低地址偏移,这符合栈内存的分配原则
    对象指针
    # 对象
    JCPerson *p1 = [JCPerson alloc];
    JCPerson *p2 = [JCPerson alloc];
    JCNSLog(@"%@ -- %p",p1,&p1);
    JCNSLog(@"%@ -- %p",p2,&p2);
    
    ==========: <JCPerson: 0x1004075a0> -- 0x7ffeefbff4e8
    ==========: <JCPerson: 0x100408570> -- 0x7ffeefbff4e0
    
    • alloc开辟的内存在堆区指针地址栈区
    • 堆区是从地址 --> 地址,栈区是从地址 --> 地址
    数组指针
    int c[4] = {1,2,3,4};
    int *d   = c;
    JCNSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
    JCNSLog(@"%p - %p - %p",d,d+1,d+2);
    
    for (int i = 0; i<4; i++) {
         int value =  *(d+i);
         JCNSLog(@"----%d",value);
    }
    
    ==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e0 - 0x7ffeefbff4e4
    ==========: 0x7ffeefbff4e0 - 0x7ffeefbff4e4 - 0x7ffeefbff4e8
    ==========: ----1
    ==========: ----2
    ==========: ----3
    ==========: ----4
    
    • 数组的地址就是元素的首地址,即&c == &c[0]
    • 数组中每个元素的地址可以通过:首地址 + n*元素类型大小来获取,只需要数组中元素数据类型相同
    • 数组中的每个元素的地址间隔是通过当前元素的数据类型决定的
    总结:
    • 内存偏移可以根据首地址 + 偏移值方式来获取各个数据的内存地址

    类结构的分析

    上面我们已经了解了内存偏移的知识,接下来我们来探究的底层结构。

    类的内存地址.png 代码分析:在对象底层结构中存放在属性成员变量等数据;从图中打印可以看出是有内存的,那么它里面存放着什么呢?接下来分析底层的数据结构。

    类的底层结构:

    探索对象isa的过程中,我们已经知道isa在底层是Class类型,Class类型是objc_class *,所有的底层实现都是objc_class,通过全局搜索可以找到如下代码:

    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;   # OBJC2不可用
    

    通过上面这段代码我们发现在OBJC2中不可用,而现在的版本基本是在用OBJC2,所以这不是我们分析的,接下来在看一下如下代码:

    struct objc_class : objc_object {
      objc_class(const objc_class&) = delete;
      objc_class(objc_class&&) = delete;
      void operator=(const objc_class&) = delete;
      void operator=(objc_class&&) = delete;
        // 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
        ...
        下面全部是方法,不需要分析,省略
    }
    

    源码分析:objc_class是继承objc_object,这说明也是对象,所谓万物皆对象。objc_class里面有一个隐藏成员变量isa,我们前面已经分析过了,下面还有三个成员变量superclass,cache,bits,我们知道首地址就是isa,那么我们可以通过首地址+偏移量的方式去获取成员变量的地址,然后获取值。

    • isa是结构体指针,占8字节
    • Class superclassClass类型,也是属于结构体指针,占8字节
    • cachecache_t结构体,结构体大小由内部的变量决定
    • bitsclass_data_bits_t结构体,如果知道前面三个成员变量的大小,那么就可以得到bits的地址

    前面三个成员变量已经知道了前两个的大小,只要知道cache的内存大小,接下来看一下cache_t的内存大小

    typedef unsigned long           uintptr_t;
    #if __LP64__
    typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
    #else
    typedef uint16_t mask_t;
    #endif
    
    struct cache_t {
    private:
        explicit_atomic<uintptr_t> _bucketsAndMaybeMask;  //8
        union {
            struct {
                explicit_atomic<mask_t>    _maybeMask;  //4
    #if __LP64__
                uint16_t                   _flags;   //2
    #endif
                uint16_t                   _occupied;  //2
            };
            explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
        };
       ...
       下面的一些方法直接省略(static类型的是在内存的全局区,不在结构体里面的内存)
    
    }
    

    cache_t是一个结构体类型,内部包含了_bucketsAndMaybeMask和一个联合体.

    • _bucketsAndMaybeMaskuintptr_t类型,而uintptr_t是无符号长整型占8个字节
    • 联合体内存大小由成员变量中的最大变量的内存大小决定,该联合体由一个结构体_originalPreoptCache两个成员变量组成,由于联合体存在互斥的,所以只需要得到其中最大变量的内存大小
    • _originalPreoptCachepreopt_cache_t *结构体指针类型,占8个字节
    • 结构体中有_maybeMask,_flags,_occupied_maybeMaskmask_t类型,mask_t又是uint32_t类型,占4个字节,_flags_occupieduint16_t类型,占2个字节

    综上所述cache_t的内存大小为16字节。

    总结:
    • isa内存地址为首地址
    • superclass地址为首地址+0x08
    • cache_t地址为首地址+0x10
    • bits地址为首地址+0x20
      类的结构图.png

    bits数据结构

    上面已经了解的基本结构,isasuperclass已经探究过了,接下来我们先来研究一下成员变量bits存储了哪些信息?

    struct objc_class : objc_object {
      objc_class(const objc_class&) = delete;
      objc_class(objc_class&&) = delete;
      void operator=(const objc_class&) = delete;
      void operator=(objc_class&&) = delete;
        // 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 getSuperclass() const {...}
        void setSuperclass(Class newSuperclass) {...}
    
        class_rw_t *data() const {
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    

    bitsclass_data_bits_t类型,底层源码中还有一个data(),返回bits.data(),这有可能就是bits中存储的数据。data()的类型是class_rw_t

    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_or_rw_ext)->ro;
            }
            return v.get<const class_ro_t *>(&ro_or_rw_ext);
        }
    
        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_or_rw_ext)->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 *>(&ro_or_rw_ext)->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->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 *>(&ro_or_rw_ext)->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
            }
        }
    

    class_rw_t是一个结构体,里面存储着方法,属性,协议列表,接下来验证是否存储在class_rw_t中。

    属性探究( properties() )
    @interface JCPerson : NSObject{
        NSString *subject;
    }
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *hobby;
    
    - (void)sayNB;
    + (void)say666;
    @end
    
    类结构中属性分析.png
    • property_list_t中存储name,hobby属性
    • p $7.get(2)会提示数组越界,没有找到成员变量subject

    问题:那么定义的subject成员变量存到哪里去了呢?

    补充:成员变量

    class_rw_t中除了有属性,方法,协议以外,还有class_ro_t结构体指针类型的ro(),在class_ro_t结构体中我们可以找到ivar_list_t类型的指针ivars成员变量会不会存在这里呢?

    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;
    
    类结构中成员变量分析.png
    源码和LLDB分析:
    • 成员变量底层实现是ivar_t,存储在class_ro_t成员变量列表
    • 系统是自动给属性添加_属性名的变量,存储在class_ro_t成员变量列表
    方法探究( methods() )
    类结构的方法分析.png
    通过LLDB的方式,可以得到定义的方法。但是发现使用get(index)的方式无法得到,�使用get(index).big()才能获取,这是为什么呢?
    struct property_t {
        const char *name;
        const char *attributes;
    };
    
    struct method_t {
        struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
        big &big() const {
            ASSERT(!isSmall());
            return *(struct big *)this;
        }
    
        SEL name() const {
            if (isSmall()) {
                return (small().inSharedCache()
                        ? (SEL)small().name.get()
                        : *(SEL *)small().name.get());
            } else {
                return big().name;
            }
        }
        const char *types() const {
            return isSmall() ? small().types.get() : big().types;
        }
        IMP imp(bool needsLock) const {
            if (isSmall()) {
                IMP imp = remappedImp(needsLock);
                if (!imp)
                    imp = ptrauth_sign_unauthenticated(small().imp.get(),
                                                       ptrauth_key_function_pointer, 0);
                return imp;
            }
            return big().imp;
        }
    
    源码分析: 属性和方法获取区别.png
    • 属性底层实现是property_t,在property_t结构体中定义了name等变量
    • 方法底层实现是method_t,在method_t结构体中定义了一个big(),通过big()获取SELIMP

    接下来我们继续打印methods,如下图。

    类结构的方法分析2.png
    • method_list_t中有对象方法,属性的setter方法getter方法
    • method_list_t中没有获取到类方法

    问题:那么类方法存储到哪里去了呢?

    补充:类方法

    对象方法存储在中,那类方法可能存储在元类

    类方法的分析.png
    • object_getClass获取到JCPerson的元类
    • 元类中method_list_t中存储着类方法
    总结:
    • 类的结构主要由isa,superclass,cache,bits组成
    • bits中存储着属性,方法,协议
    • 属性存储在property_list_t中,而成员变量存储在class_ro_t-->ivar_list_t,系统为属性自动生成的_属性名的变量也存储在class_ro_t-->ivar_list_t
    • 方法存储在method_list_t中,method_list_t主要存储着对象方法,属性的setter方法getter方法,而类方法存储在元类中的method_list_t

    相关文章

      网友评论

          本文标题:iOS底层原理探究 - 类结构分析

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