• 【iOS】—— 对象的底层结构和继承者链(isa、class)


    一、对象的底层结构

    要了解OC对象的底层结构,那么我们就得知道:OC本质底层实现转化其实都是C/C++代码

    我们知道其都是C/C++代码之后,我们只要将OC代码转换为C/C++代码之后再对其进行研究不就简单了,下面就一步一步来吧。

    1.OC编译生成C++代码的方法的两种方法

    我们先简单的创建一个OC项目,并在里边创建一个继承自NSObject的类LGPerson
    4324234

    1.1 clang

    clang -rewrite-objc main.m -o main.cpp //把⽬标⽂件编译成c++⽂件
    
    • 1

    在这里我们要知道OC中的.h文件只是一个对外共享数据的接口罢了,.m文件才是真正需要编译的文件

    这种方式比较简单直接但是如果引用到OC的系统库像UIKit之类的会报错,解决报错需要加一坨参数:

    clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
    
    • 1

    编译成功了之后就会出现这样的一个.cpp文件,这就是转换后的C++文件了:
    234234

    1.2 xcrun

    Xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些:

    xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o 
    main-arm64.cpp //(模拟器) 
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp //(⼿机) 
    
    • 1
    • 2
    • 3

    编译之后也会出现相应的.cpp文件,我没用这个所以没有截图…

    然后我们点开这个生成的对应的.cpp文件,开始研究!

    2.对象的本质

    点开.cpp文件之后在里面找到我们刚才定义的继承类:
    432423
    通过这个LGPerson类我们发现:

    • OC中的LGPerson类底层是struct LGPerson_IMPL结构体。
    • OC中@interface LGPerson : NSObjectLGPerson继承NSObject底层是typedef struct objc_object LGPerson;这样体现的。

    由此可见:OC对象的本质就是结构体。

    我们再顺藤摸瓜,找到struct NSObject_IMPL NSObject_IVARS;这个类型:
    324234

    struct NSObject_IMPL结构体事实上是一个Class类型的isa指针,那么也就是说明,每一个新定义的类,它对应的结构体中都会有一个这个Class类型的isa指针。

    并且NSObject底层是struct objc_object结构体。

    23423424
    同时我们通过查找objc源码找到:

    struct objc_class : objc_object {
        ...省略无关代码
        // Class ISA;  //ISA(从objc_object继承过来的)
        Class superclass;  //指向其父类
        cache_t cache;  //缓存
        class_data_bits_t bits;  //类的数据
        ...省略无关代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看出objc_class是继承objc_object的。

    看到这我们先来浅总结一下:

    • 每个类的底层都会有一个Class类的isa指针。
    • Class底层是struct objc_class *类型,NSObject底层是struct objc_object结构体,id底层是struct objc_object *类型。
    • struct objc_object的实现是:
    struct objc_object {
        Class _Nonnull isa __attribute__((deprecated));
    };
    
    • 1
    • 2
    • 3

    也就是说NSObject底层实现的结构体里只有一个成员变量isa,又因为Class底层是struct objc_class *类型,所以 NSObject的本质是objc_class

    • id底层实现是struct objc_object *类型,怪不得声明id类型变量时 后面不用再加"*"了,因为它定义的时候就定义为了一个Class的指针。
    • SELstruct objc_selector *类型。
    • IMPvoid (*)(void )函数指针类型。

    接下来再来看看我们定义的KCName属性的gettersetter方法的底层实现:
    423423424

    • 首先数入参LGPerson * selfSEL _cmd,所有OC方法都有这两个隐藏参数,所以我们可以在OC的方法中使用self指针,但是这两个参数对外是不显示的。
    • 然后可以看到gettersetter里是通过首地址指针+对应成员变量的地址值指针的偏移量的方式取和存的,最终通过(*(NSString **)还原为string类型。取值的过程就是:先拿到当前成员变量的地址,再去取这个地址里面所存的值

    2.1 objc_class 和 objc_object 有什么关系?

    • 结构体objc_class继承自objc_object,其中objc_object也是一个结构体,而且有一个isa属性,所以objc_class也拥有了isa属性。
    • main.cpp底层编译文件中,NSObject的isa在底层是由class定义的,其中class的底层编码来自于objc_class类型,所以NSObject也拥有了isa属性。
    • NSObject是一个类,用它来初始化一个实例对象 objc,objc满足objc_object的特性(有isa属性),主要是因为isa是由NSObject从objc_class继承过来的,而objc_class继承自objc_objectobjc_object有isa属性,所以对象都有一个isa,isa 表示指向,来自于当前的objc_object
    • objc_object是当前的根对象,所以所有的对象都拥有isa 属性。

    2.2 objc_object与对象的关系:

    • 所有对象都是以objc_object为模板继承过来的。
    • 所有对象都来自于NSObject,但是其底层是一个objc_object的结构体类型,所以objc_object与对象的关系是继承关系。

    435345345

    3.类结构

    首先看一下runtiem底层的数据结构:
    54353454

    上边说到的objc_class最终没在.cpp文件中找到,其大概就是这样:

    struct objc_class : objc_object {
        ...省略无关代码
        // Class ISA;  //ISA(从objc_object继承过来的)
        Class superclass;  //指向其父类
        cache_t cache;  //缓存
        class_data_bits_t bits;  //类的数据
        
    	class_rw_t *data() const {
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
        ...省略无关代码
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    从上面的结构体,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。

    3.1 Class ISA

    3234234

    继承自objc_object的isa指针,不仅实例对象中有,类对象中也有,占8字节。

    (1)isa指针是什么?

    isa指针保存着指向类对象的内存地址,类对象全局只有一个,因此每个类创建出来的对象都会默认有一个isa属性,保存类对象的地址,也就是class,通过class就可以查询到这个对象的属性和方法,协议等;

    isa分两种类型(isa指针是什么含义的时候):

    • 指针型isa:64位的0或者1的整体内容代表所指向的Class的地址,也就是可以通过isa的内容来获得类对象的地址。
    • 非指针型isaisa的值的部分代表Class的地址,之所以这样是因为我们在寻址过程中,只有三四十位数就可以保证我们寻找到所有Class地址了,多出来的位可以用来存储其他相关内容,来达到节省内存的目的。

    (2)isa的数据结构

    每个OC对象都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着对象或类对象内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个共用体(union)结构,同时使用位域来存储更多的信息。

    union isa_t 
    {
        Class cls;
        uintptr_t bits;
        struct {
             uintptr_t nonpointer        : 1;//->表示使用优化的isa指针
             uintptr_t has_assoc         : 1;//->是否包含关联对象
             uintptr_t has_cxx_dtor      : 1;//->是否设置了析构函数,如果没有,释放对象更快
             uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
             uintptr_t magic             : 6;//->固定值,用于判断是否完成初始化
             uintptr_t weakly_referenced : 1;//->对象是否被弱引用
             uintptr_t deallocating      : 1;//->对象是否正在销毁
             uintptr_t has_sidetable_rc  : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
            uintptr_t extra_rc          : 19;  //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
        };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • nonpointer:用来标记这个对象是不是tagpointer类型的对象,因为iOS对oc对象进行了优化处理,有些对象是tagpointer类型的,因此这些对象是没有isa指针的,tagpointer的内存一般是在栈中的,而不是在堆里面;tagpointer对象一般是NSNumber类型的数值较小的数,或NSString类型的较小的字符串。
    • has_assoc:用来标记有没有关联对象。
    • has_cxx_dtor:该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
    • shiftcls:存储的isa指针地址,也就是类对象的地址。
    • magic:用于调试器判断当前对象是真的对象还是没有初始化的空间。
    • weakly_referenced:对象是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快释放。
    • deallocating:标志对象是否正在释放内存。
    • has_sidetable_rc:标记对象是否使用了Sidetable,当对象引用计数大于10时,则需要借用该变量存储进位。
    • extra_rc:当表示该对象的引用计数值,实际上是引用计数值减1, 例如:如果对象的引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用到下面的 has_sidetable_rc

    (3)原理探索

    下面进入initInstanceIsa的探索,首先查看它的源码实现:

    inline void 
    objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
    {
        ASSERT(!cls->instancesRequireRawIsa());
        ASSERT(hasCxxDtor == cls->hasCxxDtor());
    
        initIsa(cls, true, hasCxxDtor);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    initInstanceIsa的源码实现中,主要是调用了initIsa,继续跳到initIsa的源码:

    inline void 
    objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
    { 
        ASSERT(!isTaggedPointer()); 
    
        if (!nonpointer) {
            isa = isa_t((uintptr_t)cls);
        } else {
            ASSERT(!DisableNonpointerIsa);
            ASSERT(!cls->instancesRequireRawIsa());
    
            isa_t newisa(0);
    
    #if SUPPORT_INDEXED_ISA
            ASSERT(cls->classArrayIndex() > 0);
            newisa.bits = ISA_INDEX_MAGIC_VALUE;
            // isa.magic is part of ISA_MAGIC_VALUE
            // isa.nonpointer is part of ISA_MAGIC_VALUE
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.indexcls = (uintptr_t)cls->classArrayIndex();
    #else
            newisa.bits = ISA_MAGIC_VALUE;
            // isa.magic is part of ISA_MAGIC_VALUE
            // isa.nonpointer is part of ISA_MAGIC_VALUE
            newisa.has_cxx_dtor = hasCxxDtor;
            newisa.shiftcls = (uintptr_t)cls >> 3;
    #endif
    
            // This write must be performed in a single store in some cases
            // (for example when realizing a class because other threads
            // may simultaneously try to use the class).
            // fixme use atomics here to guarantee single-store and to
            // guarantee memory order w.r.t. the class index table
            // ...but not too atomic because we don't want to hurt instantiation
            isa = newisa;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    源码主要是初始化isa,两种方式:

    • 通过cls初始化:
      nonpointer,存储着ClassMeta-Class对象的内存地址信息。
    • 通过bits初始化:
      nonpointer,进行一系列的初始化操作。

    3.2 Class superclass

    superclass指向该对象或该类的父类,class*本身也是一个指针,占8字节。

    3.3 cache_t cache

    45345345

    cache缓存,追踪进去看一下cache_t结构体的类型,而不是结构体指针类型(占8字节),就需要计算一下了:

    struct cache_t {
        struct bucket_t *_buckets; // 8  散列表
        mask_t _mask;  // 4  散列长度-1
        mask_t _occupied; // 4  已经缓存的方法数量
    }
    #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 bucket_t {
    private:
        // IMP-first is better for arm64e ptrauth and no worse for arm64.
        // SEL-first is better for armv7* and i386 and x86_64.
    #if __arm64__
        MethodCacheIMP _imp;//函数的内存地址
        cache_key_t _key;//SEL作为key
    #else
        cache_key_t _key;//SEL作为key
        MethodCacheIMP _imp;//函数的内存地址
    #endif
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    cache_t的类型,由代码中标注的出来的内存大小,可以算出最后计算出的cache类的内存大小为 12 + 2 + 2 = 16 字节。

    同时这里引入了bucket_t(散列表),cache_t哈希表结构,哈希表内部存储的bucket_tbucket_t中存储的是SELIMP的键值对。
    43242234

    3.4 class_data_bits_t bits

    class_data_bits_t作为属性bits的类型,也是个结构体,其数据结构如下:

    struct class_data_bits_t {
        friend objc_class;//这里声明objc_class为class_data_bits_t的友元类,使得objc_class可以访问class_data_bits_t中的私有方法
        uintptr_t bits;
        ......
    
    • 1
    • 2
    • 3
    • 4

    从源码中可以看到,class_data_bits_t有一个属性uintptr_t bitsbits的类型是uintptr_t类型数据,那么uintptr_t是什么数据结构呢?以下是通过查看一些资料得到的解释:

    是无符号整数类型,能够存储指针。这通常意味着它与指针的大小相同。 它可选地在C++ 11和更高版本的标准中定义。 想要一个可以保存体系结构指针类型的整数类型的常见原因是对指针执行特定于整数的操作,或者通过将指针提供为整数“句柄”来模糊指针的类型。

    这里面其实利用位域存储数据,简单来说因为这个bits跟指针大小相同,在iOS中指针大小是8个字节,也就是64位,但是通常单一数据无法用满这64位的,会有很多空位,造成空间浪费。通过位运算,把不同的数据按不同的位置存进这64位内存空间的不同位置里,这样就可以提高内存利用率。这里的bits其实就是这样的,他不只存储class_rw_t结构体指针,还存储其他的信息。

    看到这里我们获取会有一些疑惑,那就是类结构里面我们没有看到方法列表、属性列表甚至成员列表等,那这些东西存在哪里呢?由前面分析知道bits里有个class_rw_t指针,而且objc_class里面的datagetData方法都涉及到class_rw_t的读写:

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.5 class_rw_t

    4234234

    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        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;
    private:
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
        const ro_or_rw_ext_t get_ro_or_rwe() const {
            return ro_or_rw_ext_t{ro_or_rw_ext};
        }
        void set_ro_or_rwe(const class_ro_t *ro) {
            ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
        }
        void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
            // the release barrier is so that the class_rw_ext_t::ro initialization
            // is visible to lockless readers
            rwe->ro = ro;
            ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
        }
        class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
    public:
        void setFlags(uint32_t set)
        {
            __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
        }
        void clearFlags(uint32_t clear) 
        {
            __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
        }
        // set and clear must not overlap
        void changeFlags(uint32_t set, uint32_t clear) 
        {
            ASSERT((set & clear) == 0);
            uint32_t oldf, newf;
            do {
                oldf = flags;
                newf = (oldf | set) & ~clear;
            } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
        }
        class_rw_ext_t *ext() const {
            return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
        }
        class_rw_ext_t *extAllocIfNeeded() {
            auto v = get_ro_or_rwe();
            if (fastpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>();
            } else {
                return extAlloc(v.get<const class_ro_t *>());
            }
        }
        class_rw_ext_t *deepCopy(const class_ro_t *ro) {
            return extAlloc(ro, true);
        }
        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;
            }
            return v.get<const class_ro_t *>();
        }
        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 = 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 *>()->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};
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    从这个结构体可以看到,其中有const method_array_t methods()const property_array_t properties() 等这样的方法,因此可以推测类结构中的方法列表、属性列表是从这里获取的。以const method_array_t methods()为例,获取类的实例方法也是通过调用methods()来获取的。比如说runtime函数class_copyMethodList,其代码如下:

    Method *
    class_copyMethodList(Class cls, unsigned int *outCount)
    {
        unsigned int count = 0;
        Method *result = nil;
    
        if (!cls) {
            if (outCount) *outCount = 0;
            return nil;
        }
    
        mutex_locker_t lock(runtimeLock);
        const auto methods = cls->data()->methods();
        
        ASSERT(cls->isRealized());
    
        count = methods.count();
    
        if (count > 0) {
            result = (Method *)malloc((count + 1) * sizeof(Method));
            
            count = 0;
            for (auto& meth : methods) {
                result[count++] = &meth;
            }
            result[count] = nil;
        }
    
        if (outCount) *outCount = count;
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    其中const auto methods = cls->data()->methods()就是通过class_rw_tmethods()方法获取实例方法列表。但是在class_rw_t结构体中,我们并没有看到结构体里由存储方法列表相关的属性。反而是在methods()方法里看到读取方法列表是从class_rw_ext_t或者class_ro_t结构体。其实不只是methods,包括propertiesprotocols也是一样的。那么他们之间到底有什么关系呢?

    接下来我们先来看看class_rw_ext_tclass_ro_t的数据结构,看看为什么class_rw_t可以从他们这里读取类相关信息。

    3.6 class_rw_ext_t

    class_rw_ext_t是个结构体,其数据结构如下:

    struct class_rw_ext_t {
        const class_ro_t *ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        char *demangledName;
        uint32_t version;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    由此可知,结构体class_rw_ext_t中的确有方法列表、属性列表和协议等信息,而且还有一个class_ro_t指针,接下来继续看class_ro_t

    3.7 class_ro_t

    4324234

    以下是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;
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这里面我们发现class_ro_t不仅有baseMethodListbaseProtocolsbaseProperties等信息,还有成员变量const ivar_list_t * ivars等其他信息。在了解他们的结构之后,我们从结构上简单做一下它们之间关系的分析:首先class_rw_t有一个指针ro_or_rw_extro_or_rw_ext指向可能是class_rw_ext_t或者是class_ro_tro_or_rw_ext指针的作用是,当读取类相关信息时,会优先判断是否指向class_rw_ext_t,如果是class_rw_ext_t就从它读取,没有就是class_ro_t,直接从class_ro_t读取。那么在程序运行中到底ro_or_rw_ext什么时候是class_rw_ext_t,什么时候是class_ro_t呢?其实WWDC2020有过对这三个数据结构的介绍,下面会做简单的总结。

    3.8 class_ro_t & class_rw_t & class_rw_ext_t & Class之间的关系

    要了解它们之间的关系,我们首先要了解几个关键跟它们相关的概念。

    • 名词解析
      从字面意思解读class_ro_t中的“ro”代表只读;class_rw_t中的“rw”代表可读可写;class_rw_ext_t中的“rw_ext”代表可读可写的扩展。
      干净内存和脏内存:干净内存内存指的是内存一旦加载就不会被改变。反之,脏内存就是在运行时会被修改的。
    • 关系释疑
      Class本身是运行时加载的,在运行时会被改变,所以本身Class就是属于脏内存。那么如果想要获取Class的干净内存,也就是编译时确定的数据结构包括方法列表、成员变量等的,该怎么办?
      这其实就是class_ro_t的作用。因为class_ro_t是只读,意味着 class_ro_t是从mach-o读取类的数据之后,就不会被改变。那如果我们想在运行时修改类的信息,比如添加方法,比如加载category怎么办呢?那这时候就有一个与之对应的class_rw_t结构,class_rw_t可以在运行时存储类的信息,可读可写的,可以在运行时修改
      说到这里,好像还漏掉一个结构class_rw_ext_t,这个东西又是干什么用的呢?存在的意义是什么?其实还是跟运行时有关。实际上在我们的app运行中,需要运行时修改的类是非常少的,据统计平均大概就10%左右。那也就是说大部分只需要读取class_ro_t中的数据就够了,少部分才需要修改。因此才会有class_rw_ext_t这个扩展的结构体。class_rw_ext_t的作用是这样的:当我们需要修改类结构时,比如添加方法、加载category等时,class_rw_t回去开辟一个额外的空间rwe(class_rw_ext_t),用于存储新的方法和class_ro_t中的方法等信息。这样做的目的有一个好处就是,对于绝大部分类是不需要这个开辟class_rw_ext_t这个结构体,节省内存。
    • 创建时机
      那这几个结构分别是什么时候创建的呢?这里就设计到类的加载流程。首先类在app启动的时候会被映射到内存,这时候会先创建Class(objc_class)结构,然后把类编译时的类数据映射到class_ro_tclass_ro_t结构体指针存储到Classbits指针中,我们前面提到类的bits中存储的是class_rw_t指针,实际上在类初始化之前这里存储的是class_ro_t,等到类初始化的时候会创建一个class_rw_t结构,然后通过data()bits中读取class_ro_t,然后class_rw_t通过set_ro(const class_ro_t *ro)把指针ro_or_rw_ext指向这个class_ro_t,然后Class通过setData()class_rw_t指针存储到bits里面。然后在运行时根据需要,根据extAllocIfNeededextAlloc创建class_rw_ext_t,然后把class_ro_t关联到class_rw_ext_t,这就是大概流程。OC类的加载流程

    3.9 category不能添加成员变量的原因

    因为category是运行时添加的,他只能操作class_rw_t结构,但是class_rw_t结构没有添加成员变量的入口。成员变量是存储在class_ro_t中的,是无法被修改的,所以category就不能添加成员变量。

    4.类数据的存储

    通过上述的源码解析,都应该清楚了类数据的存储结构,具体的可以看看这个:iOS底层原理之类的结构分析,大概总结总结一下结构关系,如下图所示:
    4324234

    4324234

    二、继承者链

    我们先定义两个类:

    • 继承自NSObject的类LGPerson
    #import <Foundation/Foundation.h>
    
    @interface LGPerson : NSObject {
        NSString *hobby;
    }
    @property (nonatomic, copy) NSString *lg_name;
    
    - (void)sayHello;
    + (void)sayBye;
    
    @end
    
    
    #import "LGPerson.h"
    
    @implementation LGPerson
    - (void)sayHello {
        
    }
    + (void)sayBye {
        
    }
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 继承自LGPerson的类LGTeacher
    #import <Foundation/Foundation.h>
    #import "LGPerson.h"
    
    @interface LGTeacher : LGPerson
    @end
    
    
    #import "LGTeacher.h"
    
    @implementation LGTeacher
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • main中分别用两个定义两个对象:personteacher
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGPerson *person = [[LGPerson alloc] init];
            LGTeacher *teacher = [[LGTeacher alloc] init];
            NSLog(@"Hello, World! %@ - %@", person, teacher);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.元类

    首先,我们先通过一个案例的lldb调试先引入元类。

    • mainLGTeacher部分加一个断点,运行程序。
    • 开启lldb调试,调试的过程如下图所示。
      5345345
      根据图示的调试过程,我们发现:为什么图中的p/x 0x0100000100008239 & 0x00007ffffffffff8ULLp/x 0x0000000100008210 & 0x00007ffffffffff8ULL中的类信息打印出来的都是LGPerson?一个是实例变量的isa指向,一个是该类的isa指向为什么输出的是同一个类呢?
    • 0x0100000100008239person对象的isa指针地址,其&后得到的结果是创建person的类LGPerson
    • 0x0000000100008210isa中获取的类信息所指的类的isa的指针地址,即LGPerson类的类的isa指针地址,在Apple中,我们简称LGPerson类的类为元类
    • 所以,两个打印都是LGPerson的根本原因就是因为元类导致的。

    2.元类的说明

    下面来解释什么是元类:

    • 我们都知道对象的isa是指向类,类的其实也是一个对象,可以称为类对象,其isa的位域指向苹果定义的元类
    • 元类是系统给的,其定义和创建都是由编译器完成,在这个过程中,类的归属来自于元类
    • 元类是类对象 的类,每个类都有一个独一无二的元类用来存储类方法的相关信息。
    • 元类本身是没有名称的,由于与类相关联,所以使用了同类名一样的名称。

    于是有了下面的图:
    5345345
    对象 --> 类 --> 元类 --> NSobject,NSObject指向自身。

    3.NSObject到底有几个?

    从图中可以看出,最后的根元类是NSObject,这个NSObject与我们日开开发中所知道的NSObject是同一个吗?

    我们来通过代码验证一下:

    Class class1 = [LGPerson class];
    Class class2 = [LGPerson alloc].class;
    Class class3 = object_getClass([LGPerson alloc]);
    NSLog(@"\n%p-\n%p-\n%p-\n", class1, class2, class3);
    
    • 1
    • 2
    • 3
    • 4

    423423
    从结果中可以看出,打印的地址都是同一个,所以NSObject只有一份,即NSObject(根元类)在内存中永远只存在一份,由于类的信息在内存中永远只存在一份,所以类对象只有一份。

    4.著名的isa走位 & 继承关系图

    根据上面的探索以及各种验证,对象、类、元类、根元类的关系如下图所示:
    5345345345

    4.1 isa走位

    • 实例对象(Instance of Subclass)isa指向类(class)
    • 类对象(class)isa指向元类(Meta class)
    • 元类(Meta class)isa指向根元类(Root metal class)
    • 根元类(Root metal class)isa指向它自己本身,形成闭环,这里的根元类就是NSObject

    4.2 superclass走位

    之间的继承关系:

    • 类(subClass)继承自父类(superClass)
    • 父类(superClass)继承自根类(RootClass),此时的根类是指NSObject
    • 根类继承自nil,所以根类即NSObject可以理解为万物起源,即无中生有

    元类也存在继承,元类之间的继承关系如下:

    • 子类的元类(metal SubClass)继承自父类的元类(metal SuperClass)
    • 父类的元类(metal SuperClass)继承自根元类(Root metal Class)
    • 根元类(Root metal Class)继承于根类(Root class),此时的根类是指NSObject

    【注意】实例对象之间没有继承关系,类之间有继承关系。

    对象方法,属性,成员变量,协议信息 存放在class对象中。
    类方法,存放在meta-class对象中。
    成员变量的具体值,存放在instance对象中。

    我可能有的说的还不太清楚,理解了一部分之后可以看看这个博客:iOS runtime总结数据结构,消息传递、转发和应用场景,我觉得说的很容易理解。

  • 相关阅读:
    简单好用的轻量级思维导图:ClickCharts 激活for mac
    【正点原子STM32连载】第五章 STM32基础知识入门 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
    [附源码]计算机毕业设计农产品销售网站Springboot程序
    [Python]多任务编程--线程
    idea模板设置
    第三章 探索组件的理念
    ubuntu安装ch34x驱动,并安装串口调试助手
    [2022 广东省赛M] 拉格朗日插值 (多元函数极值 分治NTT)
    菜鸟踩坑之@PostMapping怎么接收String类型的参数
    Linux 环境安装【jdk、Tomcat、Docker、Maven、Kafka、Redis等】
  • 原文地址:https://blog.csdn.net/m0_55124878/article/details/125808836