美文网首页
iOS底层之类结构分析

iOS底层之类结构分析

作者: 当沉默已成习惯 | 来源:发表于2020-09-13 19:01 被阅读0次

    上篇文章: iOS底层之isa走位探索

    前言

    从上篇文章中我们了解了对象的isa指针的走位逻辑,接下来咱们分析一下类的结构。

    一、内存偏移

    在咱们分析类结构之前,咱们先来了解一下内存偏移的知识。咱们先看一个例子

    void pointOffset(){
        int arr[4] = {1, 3, 5, 6};
        int *p = arr;
                
        for (int i=0; i<4; i++) {
            NSLog(@"%p -- %d", p+i, arr[i]);
        }
    }
    

    打印结果为

    0x7ffeefbff4e0 -- 1
    0x7ffeefbff4e4 -- 3
    0x7ffeefbff4e8 -- 5
    0x7ffeefbff4ec -- 6
    

    从打印结果可以看出这四个内存地址是连续的,切每个地址相差4字节,因为int类型的内存大小为4字节。也就是说如果我们知道一个对象的首地址,且知道其后排列的每个元素的内存大小,那么我们就可以知道后面每个元素的大小。接下来我们来验证一下

    int数组内存偏移 如上图所示,如果我们知道了首地址,并且知道后面每一个元素的大小,我们就可以推出后面的元素的内存地址。如果对lldb命令不熟的同学可以看下lldb内存读取这篇文章。
    可能有的同学认为这个例子有点简单,且数组中的元素的内存大小一致,无法说明问题,如果是每个元素不一致又该怎么办。
    为了更具有说服力,接下来咱们举一个复杂的例子
    struct StructPointTest {    //内存大小      内存所在地址    比首地址多几个字节
        double a;               // 8            (0-7)           0字节
        short b;                // 2            (8-9)           8字节
        int c;                  // 4            (12-15)         12字节
        struct Struct2 d;       //16            (16-31)         16字节
        WJPerson *e;            // 8            (32-39)         32字节
        
    }structPointTest;
    

    根据这篇iOS底层之内存对齐文章,咱们能够知道StructPointTest结构体中每个元素的内存大小内存所在地址以及比首地址多几个字节,咱们就用这个结构体来验证一下

    struct StructPointTest str = {1.0, 3, 2, {4,5,'a',7}, [WJPerson alloc]};
            
    NSLog(@"\n%p -- %f \n%p -- %d \n%p -- %d \n%p -- \n%p -- %@", &str.a, str.a, &str.b, str.b, &str.c, str.c, &str.d, &str.e, str.e);
    

    咱们首先打印下str所在的首地址

    StructPointTest结构体首地址 根据咱们上面得出的结论我们可以推导出StructPointTest里各个元素的地址:
    • str.a 的地址为str的首地址就是0x00007ffeefbff550
    • str.b 的地址比首地址多8个字节,推出地址为0x00007ffeefbff558
    • str.c 的地址比首地址多12个字节,推出地址为0x00007ffeefbff55c
    • str.d 的地址比首地址多16个字节,推出地址为0x00007ffeefbff560
    • str.e 的地址比首地址多32个字节,推出地址为0x00007ffeefbff570

    接下来咱们使用lldb调试打印一下这些地址

    相关地址的值 我们再看一下NSLog打印的值
    0x7ffeefbff550 -- 1.000000 
    0x7ffeefbff558 -- 3 
    0x7ffeefbff55c -- 2 
    0x7ffeefbff560 -- 
    0x7ffeefbff570 -- <WJPerson: 0x1006b1110>
    

    由此我们可以得出结论:只要知道一个对象的首地址的值,就可以根据对象中元素的内存大小推导出每个元素的内存地址

    二、类结构分析

    我们在最新的objc4源码中搜索objc_class会发现两个版本的结构体定义,一个是runtime.h文件里定义的老版的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;        //已废弃的
    

    一个是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
    
        //....方法部分已省略,目前显示的是objc_class的
    }
    

    从新版的定义中,可以看到 objc_class 结构体类型是继承自 objc_object的。

    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    

    接下来我们分析一下新版的objc_class

    1、类结构分析之bits

    从最新的objc_class的定义中我们知道objc_class中有4个成员变量,分别为isasuperclasscachebitsisasuperclass我们已经了解了,cache我们根据名字就可以知道是缓存信息,那么class_data_bits_t bits;中又存放了一下什么信息呢。
    正常情况下我们无法直接访问objc_class中的bits内容,要想了解bits的信息,我们就需要想办法访问bits所在的内存空间,这时候就需要用到我们上文提到的内存偏移的知识了。
    我们已经知道isasuperclass的内存大小都是8字节,那么cache又占了多少字节呢。我们先看下cache_t中除去static修饰的静态变量和方法外还有什么。

    struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;    //8字节
        explicit_atomic<mask_t> _mask;                  //4字节
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets;     //8字节
        mask_t _mask_unused;                            //4字节
    #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;     //8字节
        mask_t _mask_unused;                            //4字节
    #else
    #error Unknown cache mask storage type.
    #endif
    #if __LP64__
        uint16_t _flags;                                //2字节
    #endif
        uint16_t _occupied;                             //2字节
    }
    

    可以看到第一个#if#endif直接不管满足什么条件都是12字节,如果第二个#if满足条件就是16字节,否则就是14字节,但是不管是14字节还是16字节,根据内存对齐原则cache_t的大小都是16字节。所以我们最后得出结论:cache_t的大小为16字节
    接下来我们通过lldb打印一下bits的信息。

    @interface WJPerson : NSObject
    {
        NSString *habby;
    }
    
    @property (nonatomic, copy) NSString *name;
    
    - (void)sayHello;
    
    + (void)sayGoodbye;
    
    @end
    
    @implementation WJPerson
    
    - (void)sayHello{}
    
    + (void)sayGoodbye{}
    
    @end
    

    我们先给WJPerson添加一下信息,然后再看一下WJPerson的信息。
    我们先来看下class_data_bits_t都有什么信息

    struct class_data_bits_t {
        friend objc_class;
    
        // Values are the FAST_ flags above.
        uintptr_t bits;
    private:
        bool getBit(uintptr_t bit) const
        {
            return bits & bit;
        }
    
        // Atomically set the bits in `set` and clear the bits in `clear`.
        // set and clear must not overlap.
        void setAndClearBits(uintptr_t set, uintptr_t clear)
        {
            ASSERT((set & clear) == 0);
            uintptr_t oldBits;
            uintptr_t newBits;
            do {
                oldBits = LoadExclusive(&bits);
                newBits = (oldBits | set) & ~clear;
            } while (!StoreReleaseExclusive(&bits, oldBits, newBits));
        }
    
        void setBits(uintptr_t set) {
            __c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
        }
    
        void clearBits(uintptr_t clear) {
            __c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
        }
    
    public:
    
        class_rw_t* data() const {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
        void setData(class_rw_t *newData)
        {
            ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
            // Set during realization or construction only. No locking needed.
            // Use a store-release fence because there may be concurrent
            // readers of data and data's contents.
            uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
            atomic_thread_fence(memory_order_release);
            bits = newBits;
        }
    
        // Get the class's ro data, even in the presence of concurrent realization.
        // fixme this isn't really safe without a compiler barrier at least
        // and probably a memory barrier when realizeClass changes the data field
        const class_ro_t *safe_ro() {
            class_rw_t *maybe_rw = data();
            if (maybe_rw->flags & RW_REALIZED) {
                // maybe_rw is rw
                return maybe_rw->ro();
            } else {
                // maybe_rw is actually ro
                return (class_ro_t *)maybe_rw;
            }
        }
    
        void setClassArrayIndex(unsigned Idx) {
    #if SUPPORT_INDEXED_ISA
            // 0 is unused as then we can rely on zero-initialisation from calloc.
            ASSERT(Idx > 0);
            data()->index = Idx;
    #endif
        }
    
        unsigned classArrayIndex() {
    #if SUPPORT_INDEXED_ISA
            return data()->index;
    #else
            return 0;
    #endif
        }
    
        bool isAnySwift() {
            return isSwiftStable() || isSwiftLegacy();
        }
    
        bool isSwiftStable() {
            return getBit(FAST_IS_SWIFT_STABLE);
        }
        void setIsSwiftStable() {
            setAndClearBits(FAST_IS_SWIFT_STABLE, FAST_IS_SWIFT_LEGACY);
        }
    
        bool isSwiftLegacy() {
            return getBit(FAST_IS_SWIFT_LEGACY);
        }
        void setIsSwiftLegacy() {
            setAndClearBits(FAST_IS_SWIFT_LEGACY, FAST_IS_SWIFT_STABLE);
        }
    
        // fixme remove this once the Swift runtime uses the stable bits
        bool isSwiftStable_ButAllowLegacyForNow() {
            return isAnySwift();
        }
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() {
            // This function is called on un-realized classes without
            // holding any locks.
            // Beware of races with other realizers.
            return safe_ro()->swiftMetadataInitializer();
        }
    };
    

    通过上面代码我们发现,除了class_rw_t* data()const class_ro_t *safe_ro()返回了对象外,剩下的返回值就是bool类型、void类型或基础数据类型。所以我们接下来主要看class_rw_t* data()const class_ro_t *safe_ro()
    通过上面的分析计算得出bits比首地址多8(isa)+8(superclass)+16(cache)也就是32个字节。接下来我们实际操作获取下信息。
    我们先获取下class_rw_t* data()的信息

    class_rw_t* data()返回值 接下来我们看一下class_ro_t里有什么
    struct 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};
            }
        }
    }
    

    可以看到在class_rw_t中有methodspropertiesprotocols。我们在类中定义了一些属性和方法看下能不能在这里看到。

    methods的信息 methods方法列表 属性列表
    从上面结果可以看出class_rw_t确实包含了一下属性和方法,不过只包含了我们添加的属性实例方法和属性的settergetter方法,那么我们定义的成员变量类方法呢,是不是放在了class_ro_t里面了呢,接下来我们看一下class_ro_t里的信息
    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;
    
        // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
        _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                return _swiftMetadataInitializer_NEVER_USE[0];
            } else {
                return nil;
            }
        }
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    
        class_ro_t *duplicate() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
                return ro;
            } else {
                size_t size = sizeof(*this);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                return ro;
            }
        }
    };
    

    发现class_ro_t还有baseMethodListbaseProtocolsivars方法协议属性等信息。我们再来打印一下这些信息

    class_ro_t中的baseMethodList信息 class_ro_t中的方法列表 class_ro_t中的ivars信息 class_ro_t的属性列表 通过实验我们发现在class_ro_t中的ivars存的是成员变量属性baseMethodList存的和class_rw_t中的methods一模一样。
    那么WJPerson中的类方法哪去了呢?
    预知后事如何,且听下会分解。

    相关文章

      网友评论

          本文标题:iOS底层之类结构分析

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