美文网首页
iOS 类原理探索:类的结构分析

iOS 类原理探索:类的结构分析

作者: SpringSunLcy | 来源:发表于2021-07-17 11:45 被阅读0次

    OC 类原理探索 系列文章

    1. OC 类原理探索:类的结构分析
    2. OC 类原理探索:类结构分析补充
    3. OC 类原理探索:属性的底层原理

    前言

    上一篇 OC 对象原理探索(三):对象的本质 & isa,介绍了isa的结构,关联到了类,这篇文章主要对类的结构进行分析。

    一、isa 分析到元类

    先通过lldb调试进行一波探索:

    image.png

    如上图:通过isa & isa的掩码,最终得到了SSLPerson,但是类中的isa & isa的掩码也得到了SSLPerson这是为什么呢?

    是不是内存中存了很多个SSLPerson类对象呢,下面我们来验证一下:

    Class class1 = [SSLPerson class];
    Class class2 = [SSLPerson alloc].class;
    Class class3 = object_getClass([SSLPerson alloc]);
    Class class4 = [SSLPerson alloc].class;
    NSLog(@"\n%p-\n%p-\n%p-\n%p-",class1,class2,class3,class4);
    
    打印结果:
    0x1000081f0-
    0x1000081f0-
    0x1000081f0-
    0x1000081f0-
    

    打印的地址都是相同的,可以判断SSLPerson类对象在内存中只有一个。

    我们用MachOView打开本项目的MachO文件 ->Symbol Table->*Symbols-> 搜索框输入class -> 向下滑:

    106931623940431_.pic_hd.jpg
    • __OBJC_CLASS_RO_$_SSLPerson是我们创建的SSLPerson类。
    • __OBJC_METACLASS_RO_$_SSLPerson并不是我们创建的,它是系统创建的SSLPersonMETACLASS元类),类对象的isa指针就指向了元类,那么元类的isa指针又指向哪儿里呢。

    二、isa 走位图和继承链

    1. isa 的走位链

    SSLPerson走位打印情况:

    // SSLPerson 实例对象
    SSLPerson *person = [SSLPerson alloc];
    // SSLPerson 类对象
    Class class = object_getClass(person);
    // SSLPerson 元类
    Class metaClass = object_getClass(class);
    // SSLPerson 根元类
    Class rootMetaClass = object_getClass(metaClass);
    // SSLPerson 根根元类
    Class rootRootMetaClass = object_getClass(rootMetaClass);
    
    NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p",person,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    打印结果:
    实例对象:0x101044470 
    类:0x1000081f0 
    元类:0x1000081c8 
    根元类:0x7fff80843fe0 
    根根元类:0x7fff80843fe0
    

    看打印结果,根元类根根元类的地址都是0x7fff80843fe0。可以得出SSLPersonisa指向关系:实例isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类

    NSObject走位打印情况:

    // NSObject 实例对象
    NSObject *object = [NSObject alloc];
    // NSObject 类
    Class class = object_getClass(object);
    // 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 ",object,class,metaClass,rootMetaClass,rootRootMetaClass);
    
    打印结果:
    实例对象:0x100562780 
    类:0x7fff80844008 
    元类:0x7fff80843fe0 
    根元类:0x7fff80843fe0 
    根根元类:0x7fff80843fe0 
    

    看打印结果,元类根元类根根元类的地址都是0x7fff80843fe0。可以得出NSObjectisa指向关系:实例isa -> 类 isa -> 根元类 isa -> 根元类。可以发现NSObject跟普通类有所不同,isa指针直接指向了根元类

    根据上面的分析可以得到isa的走位图:

    image.png

    2. 继承链

    看下面的打印结果:

    // SSLStudent 元类
    Class tMetaClass = object_getClass(SSLStudent.class);
    // SSLStudent 元类的父类
    Class tSuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"SSLStudent 元类:%p",tMetaClass);
    NSLog(@"SSLStudent 元类的父类:%p",tSuperClass);
        
    // LGPerson 元类
    Class pMetaClass = object_getClass(SSLPerson.class);
    // LGPerson 元类的父类
    Class pSuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"SSLPerson 元类:%p",pMetaClass);
    NSLog(@"SSLPerson 元类的父类:%p",pSuperClass);
        
    // NSObject 元类
    Class nMetaClass = object_getClass(NSObject.class);
    // NSObject 元类的父类
    Class nSuperClass = class_getSuperclass(nMetaClass);
    NSLog(@"NSObject 元类:%p",nMetaClass);
    NSLog(@"NSObject 元类的父类:%p",nSuperClass);
    
    // NSObject 父类
    Class superClass = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject :%p",NSObject.class);
    NSLog(@"NSObject 父类:%p",superClass);
    
    打印结果:
    SSLStudent 元类:0x100008178
    SSLStudent 元类的父类:0x1000081c8
    SSLPerson 元类:0x1000081c8
    SSLPerson 元类的父类:0x7fff80843fe0
    NSObject 元类:0x7fff80843fe0
    NSObject 元类的父类:0x7fff80844008
    NSObject :0x7fff80844008
    NSObject 父类:0x0
    
    • SSLStudent 元类的父类SSLPerson 元类的地址都是0x1000081c8
    • SSLPerson 元类的父类NSObject 元类的地址都是0x7fff80843fe0
    • NSObject 元类的父类NSObject的地址都是0x7fff80844008
    • NSObject 父类是空。

    因此可以得到类的继承链:

    image.png

    3.官方走位继承图

    isa流程图.png

    三、源码分析类的结构

    我们打开源码(objc4-818版本)搜索struct objc_class查看类结构。

    1. 废弃的类结构

    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_UNAVAILABLE,说明这个版本在2.0中已经废弃了,我们就不用再看这个版本。

    2. 源码类结构

    现在用的类结构版本在objc-runtime-new.h中,定义如下:

    struct objc_class : objc_object {
        xxx...
        // 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();
        }
        xxx...
    }
    

    可以看到类是一个objc_class类型的结构体,继承于objc_object,我们之前文章已经了解到objc_object结构体中有个isa_t类型的isa成员变量。

    objc_class中的成员变量:

    • isa:隐藏变量,继承于objc_object
    • superclass:类指针,指向父类;
    • cache:缓存;
    • bits:内存数据。

    四、内存偏移(知识补充)

    // 基本类型
    int a = 10;
    // 对象类型
    SSLPerson *person = [SSLPerson alloc]; 
    NSLog(@"%p -- %d",&a,a);
    NSLog(@"%p -- %@",&person,person);
    
    打印结果:
    0x7ffeefbff3ac -- 10
    0x7ffeefbff3a0 -- <SSLPerson: 0x100728a20>
    

    a指针指向10的地址,person指针指向[SSLPerson alloc]开辟的内存地址,同时&person指针又指向了person指针的地址。

    如图:

    image.png
    // 数组指针
    int c[4] = {1,2,3,4};
    int *d   = c;
    NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
    NSLog(@"%p - %p - %p",d,d+1,d+2);
    
    for (int i = 0; i<4; i++) {
        int value =  *(d+i);
        NSLog(@"%d",value);
    }
    
    打印结果:
    0x7ffeefbff3a0 - 0x7ffeefbff3a0 - 0x7ffeefbff3a4
    0x7ffeefbff3a0 - 0x7ffeefbff3a4 - 0x7ffeefbff3a8
    1
    2
    3
    4
    
    • dcc[0]都指向了数组的首地址0x7ffeefbff3a0
    • 对地址进行加1操作就是地址偏移了sizeof(int)的大小,得到下一个元素的地址0x7ffeefbff3a40x7ffeefbff3a80x7ffeefbff3ac
    • 通过*(指针)操作得到地址所存储的值。
    image.png

    五、类结构的内存计算

    1. 计算 bits 偏移大小

    上面我们知道类结构中有isasuperclasscachebits四个成员变量。isa8个字节,superclass是结构体指针也占8个字节,下面来看下cache占多少个字节。

    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
        };
    }
    
    • cache_t的大小为_bucketsAndMaybeMaskunion的和。
    • 点进explicit_atomic<uintptr_t>中可以看到初始化和方法都是范型,所以_bucketsAndMaybeMask的大小就是<uintptr_t>的大小为8字节;
    • 也可得出union的大小为8字节,整个cache_t结构体的大小就是16字节。

    所以得到,从类的首地址偏移到bits需要8 + 8 + 16 = 32个字节也就是0x20

    2. 通过偏移获取 bits

    lldb进行调试获取bitsclass_rw_t的值:

    image.png

    3. bts 中 class_rw_t 结构分析

    接下来我们要探究class_rw_t方法属性的存储,打开源码,看一下class_rw_t中有些什么:

    struct class_rw_t {
        xxx...
        
        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};
            }
        }
    }
    
    • methods():获取方法函数,返回值为method_array_t类;
    • properties():获取属性,返回值为property_array_t类;

    看下method_array_tproperty_array_t的定义:

    class method_array_t : 
        public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
    {
        xxx...
    };
    
    class property_array_t : 
        public list_array_tt<property_t, property_list_t, RawPtr>
    {
        xxx...
    };
    
    template <typename Element, typename List, template<typename> class Ptr>
    class list_array_tt {
        xxx...
    }
    
    struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
    };
    struct property_t {
        const char *name;
        const char *attributes;
    };
    
    struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
        xxx...
    }
    struct method_t {
        xxx...
        struct big {
            SEL name;
            const char *types;
            MethodListIMP imp;
        };
        xxx...
    }
    
    template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
    struct entsize_list_tt {
        xxx...
        Element& getOrEnd(uint32_t i) const { 
            ASSERT(i <= count);
            return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
        }
        Element& get(uint32_t i) const { 
            ASSERT(i < count);
            return getOrEnd(i);
        }
        xxx...
    }
    
    • property_array_tmethod_array_t继承自list_array_tt
    • list_array_ttList对应property_list_tmethod_list_t
    • property_list_tmethod_list_t继承自entsize_list_tt
    • entsize_list_tt中有get()方法可以获取列表中元素;
    • property_t中有nameattributes成员变量;
    • method_t中有big结构体,结构体中有nametypesimp成员变量。

    4. lldb 查看属性列表:property_list_t

    image.png

    properties()函数,最终得到了属性nameage,并没有获取到_hoby成员变量;

    5. lldb 查看方法列表:method_list_t

    image.png

    methods()函数,最终得到了方法namesetNameagesetAgeeat,并没有获取到+ (void)run

    6. lldb 查找类方法

    类方法存储在元类中,lldb验证:

    image.png

    7. 成员变量结构分析

    image.png
    • 最终找到ivar_list_t * ivars变量;
    • ivar_list_tmethod_list_tproperty_list_t一样都继承自entsize_list_tt

    8. lldb 查看成员变量:ivar_list_t

    image.png

    相关文章

      网友评论

          本文标题:iOS 类原理探索:类的结构分析

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