美文网首页IOS开发知识点
iOS之类的结构分析

iOS之类的结构分析

作者: 过客Zhaopy | 来源:发表于2020-09-13 22:33 被阅读0次

    万物皆对象

    我们知道在iOS中,id可以指向所有的实例对象,Class可以指向所有的类,我们来看一下他们的声明:

    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    从声明中可以看出,OC的所有实例对象都是由objc_object结构体扩展而来,我们知道objc_object结构体中有一个isa指针,所以所有的对象都有isa指针。
    我们再来看一下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
    
        class_rw_t *data() const {
            return bits.data();
        }
        省略。。。
    }
    

    objc_class的结构可以得出,OC中所有的其实也是对象,也存在isa指针,所以这就是我们常说的万物皆对象

    对象、类、元类、根元类

    在前一篇iOS之isa文章的最后,插入了一张图,如下:

    类的继承.png
    很多iOS开发者,肯定不止一次见到过这张图,我想肯定有人会有疑问,真的像图中这样吗?下面我们验证一下。
    @interface PYTeacher : NSObject
    
    @end
    
    @implementation PYTeacher
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            PYTeacher *teacher = [PYTeacher alloc];
            NSLog(@"%@", teacher);
        }
        return 0;
    }
    

    依然是上面这段代码,我们可以通过打印对象的内存分布来验证,依然以x86_64为例,ISA_MASK0x00007ffffffffff8ULL

    对象的 isa 指向类

    (lldb) x/2gx teacher
    0x100712980: 0x001d800100002359 0x0000000000000000
    // isa & ISA_MASK
    (lldb) po 0x001d800100002359 & 0x00007ffffffffff8ULL
    PYTeacher
    

    从上面的打印结果,我们得出 teacher对象的 isa 指向 PYTeacher 类:

    image.png

    类的 isa 指向元类

    由第一节我们得出,类也是对象,也存在isa指针,那类的isa指针指向哪里呢?我们打印一下类的isa

    (lldb) x/2gx PYTeacher.class
    0x100002358: 0x0000000100002330 0x00007fff940b3118
    
    // 打印 PYTeacher 的元类
    (lldb) po 0x0000000100002330 & 0x00007ffffffffff8ULL
    PYTeacher
    

    从打印结果,我们看到,PYTeacherisa也指向PYTeacher,那这两个PYTeacher类是同一个吗?我们打印一下他们各自的地址:

    (lldb) p/x 0x0000000100002330 & 0x00007ffffffffff8ULL
    // PYTeacher元类的地址
    (unsigned long long) $4 = 0x0000000100002330
    
    (lldb) p/x PYTeacher.class
    // PYTeacher类的地址
    (Class) $5 = 0x0000000100002358 PYTeacher
    

    我们看到这两个PYTeacher的地址并不相同,实际上PYTeacherisa指向了他的元类 PYTeacher

    image.png

    元类的 isa 指向根元类

    得到上面isa的指向流程以后,我们肯定会想,元类的 isa 指向谁呢?我们还是通过打印来看一下:

    (lldb) x/2gx 0x0000000100002330 // PYTeacher元类的地址
    0x100002330: 0x00007fff940b30f0 0x00007fff940b30f0
    
    // 打印根元类
    (lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
    NSObject
    

    从打印结果看,元类的isa指向NSObject,这时,我们会想这个NSObject是我们常用的那个NSObject吗?我们来验证一下:

    (lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $7 = 0x00007fff940b30f0 // 刚才得出的NSObject的地址
    
    (lldb) p/x NSObject.class
    (Class) $8 = 0x00007fff940b3118 NSObject // 我们常用的NSObject的地址
    
    (lldb) x/2gx NSObject.class
    0x7fff940b3118: 0x00007fff940b30f0 0x0000000000000000
    
    (lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $10 = 0x00007fff940b30f0 // NSObject的元类的地址
    

    我们看到刚才得出的NSObjectNSObject元类,也就是根元类

    image.png

    根元类的 isa 指向自己

    验证到这里,肯定有人会想,根元类的 isa 又指向哪里呢?我们继续验证:

    (lldb) x/2gx 0x00007fff940b30f0 // 根元类的地址
    0x7fff940b30f0: 0x00007fff940b30f0 0x00007fff940b3118
    
    (lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
    (unsigned long long) $11 = 0x00007fff940b30f0 // 根元类isa指向的地址
    
    (lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
    NSObject
    

    打印结果得出:根元类的 isa 指向了自己。

    image.png

    类的结构

    我们在iOS的源码中看到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
    
        class_rw_t *data() const {
            return bits.data();
        }
        省略。。。
    }
    

    objc_class的成员变量在内存中的分布如下:

    image.png
    从内存分布可以看出,我们可以通过移动pointer(类的地址指针),来读取不同地址的信息。我们知道 isa8 个字节,所以对 pointer移动8个字节,就可以读取superclass的内容,下面我们可以验证一下:
    @interface PYEnglishTeacher : PYTeacher
    @end
    
    @implementation PYEnglishTeacher
    @end
    

    我们创建了一个PYEnglishTeacher类继承自PYTeacher类:

    (lldb) p/x PYEnglishTeacher.class
    (Class) $0 = 0x0000000100002438 PYEnglishTeacher
    
    (lldb) po 0x0000000100002438 + 8
    <PYTeacher: 0x100002440>
    

    所以,对PYEnglishTeacher类的地址移动8个字节,就可以读取到superclass

    Methods

    我们发现在objc_class中,有isasuperclasscachebits等成员变量,但是我们自己声明的属性、方法、协议存储在哪呢?我们知道,在以前的objc_class定义中,是有objc_ivar_listobjc_method_listobjc_cacheobjc_protocol_list等成员变量的,那现在苹果是如何设计的呢?我们发现objc_class中有class_rw_t *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;
            }
            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};
            }
        }
    };
    

    class_rw_t 的结构,我们得出可以通过 class_rw_t 来读取方法列表等。所以首先我们要知道,需要移动多少字节,才能读取objc_class中的bits
    我们知道isa为8字节,superclass为8字节,那cache是多少字节呢?我们看一下cache_t的结构:

    struct cache_t {
    
        explicit_atomic<struct bucket_t *> _buckets;  // 8字节
        explicit_atomic<mask_t> _mask;  // 4字节
        省略。。
    #if __LP64__
        uint16_t _flags;  // 2字节
    #endif
        uint16_t _occupied; // 2字节
    }
    

    所以 cache 是16字节。所以pointer移动32个字节就是bits的地址。我们打印一下PYTeacher

    (lldb) p/x PYTeacher.class
    (Class) $0 = 0x0000000100002208 PYTeacher
    
    // 打印class_data_bits_t bits地址
    (lldb) p (class_data_bits_t *)(0x0000000100002208 + 32)
    (class_data_bits_t *) $1 = 0x0000000100002228
    

    $1class_data_bits_t结构体的指针,通过调用data()方法可以得到class_rw_t的指针:

    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x00000001021100b0
    

    我们打印$2指向的class_rw_t结构体:

    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975600
      }
      firstSubclass = PYEnglishTeacher
      nextSiblingClass = NSUUID
    }
    

    现在我们PYTeacher添加一些方法和属性:

    @interface PYTeacher : NSObject {
        NSString *name;
    }
    
    @property (nonatomic, assign) int age;
    
    - (void)teach;
    + (void)study;
    
    @end
    
    @implementation PYTeacher
    
    - (void)teach {
        
    }
    
    + (void)study {
        
    }
    
    @end
    

    这时,我们再打印class_rw_t的内容:

    (lldb) p/x PYTeacher.class
    (Class) $0 = 0x0000000100002300 PYTeacher
    
    // 打印bits的地址
    (lldb) p (class_data_bits_t *)(0x0000000100002300 + 32)
    (class_data_bits_t *) $1 = 0x0000000100002320
    
    // 获取class_rw_t的指针
    (lldb) p $1->data()
    (class_rw_t *) $2 = 0x0000000100677d80
    
    // 打印 class_rw_t 内容
    (lldb) p *$2
    (class_rw_t) $3 = {
      flags = 2148007936
      witness = 0
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975632
      }
      firstSubclass = PYEnglishTeacher
      nextSiblingClass = NSUUID
    }
    
    // 获取class_rw_t中的方法列表
    (lldb) p $3.methods()
    (const method_array_t) $4 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x00000001000020d8
          arrayAndFlag = 4294975704
        }
      }
    }
    
    // 获取method_list_t的数组指针
    (lldb) p $4.list
    (method_list_t *const) $5 = 0x00000001000020d8
    
    // 打印method_list_t数组,其实就是数组的第0位
    (lldb) p *$5
    (method_list_t) $6 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "teach"
          types = 0x0000000100000f74 "v16@0:8"
          imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
        }
      }
    }
    
    // 打印数组中的内容
    (lldb) p $6.get(0)
    (method_t) $7 = {
      name = "teach"
      types = 0x0000000100000f74 "v16@0:8"
      imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
    }
    
    (lldb) p $6.get(1)
    (method_t) $8 = {
      name = ".cxx_destruct"
      types = 0x0000000100000f74 "v16@0:8"
      imp = 0x0000000100000e30 (KCObjc`-[PYTeacher .cxx_destruct] at main.m:22)
    }
    
    (lldb) p $6.get(2)
    (method_t) $9 = {
      name = "age"
      types = 0x0000000100000f8a "i16@0:8"
      imp = 0x0000000100000df0 (KCObjc`-[PYTeacher age] at main.m:15)
    }
    
    (lldb) p $6.get(3)
    (method_t) $10 = {
      name = "setAge:"
      types = 0x0000000100000f92 "v20@0:8i16"
      imp = 0x0000000100000e10 (KCObjc`-[PYTeacher setAge:] at main.m:15)
    }
    

    我们看到方法列表中有4个方法,但是没有
    + (void)study方法,为什么呢?
    仔细观察我们会发现,这4个方法,除了1个C++的方法外,其他3个都是对象方法,也就是对象方法存在类的方法列表里,我们说万物皆对象元类对象,难道说类对象”对象方法“存在元类的方法列表里?下面我们验证一下:

    (lldb) x/2gx PYTeacher.class
    0x100002300: 0x00000001000022d8 0x0000000100334140
    (lldb) po 0x00000001000022d8 & 0x00007ffffffffff8ULL
    PYTeacher
    
    // 打印 PYTeacher元类 的地址
    (lldb) p/x 0x00000001000022d8 & 0x00007ffffffffff8ULL
    (unsigned long long) $13 = 0x00000001000022d8
    
    // 打印元类中的 bits
    (lldb) p (class_data_bits_t *)(0x00000001000022d8 + 32)
    (class_data_bits_t *) $14 = 0x00000001000022f8
    
    // 获取 class_rw_t 的地址
    (lldb) p $14->data()
    (class_rw_t *) $15 = 0x0000000100677d40
    
    // 打印 class_rw_t 的内容
    (lldb) p *$15
    (class_rw_t) $16 = {
      flags = 2684878849
      witness = 1
      ro_or_rw_ext = {
        std::__1::atomic<unsigned long> = 4294975528
      }
      firstSubclass = 0x0000000100002328
      nextSiblingClass = 0x00007fff8b968cd8
    }
    
    // 获取 元类 的方法列表
    (lldb) p $16.methods()
    (const method_array_t) $17 = {
      list_array_tt<method_t, method_list_t> = {
         = {
          list = 0x0000000100002070
          arrayAndFlag = 4294975600
        }
      }
    }
    
    // 打印方法列表的地址
    (lldb) p $17.list
    (method_list_t *const) $18 = 0x0000000100002070
    
    // 打印方法列表信息
    (lldb) p *$18
    (method_list_t) $19 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 1
        first = {
          name = "study"
          types = 0x0000000100000f74 "v16@0:8"
          imp = 0x0000000100000dd0 (KCObjc`+[PYTeacher study] at main.m:28)
        }
      }
    }
    

    正如我们猜测的那样,+ (void)study方法存在元类方法列表中。

    Properties

    我们再来看一下property

    (lldb) p $3.properties()
    (const property_array_t) $20 = {
      list_array_tt<property_t, property_list_t> = {
         = {
          list = 0x0000000100002188
          arrayAndFlag = 4294975880
        }
      }
    }
    
    // 打印属性列表地址
    (lldb) p $20.list
    (property_list_t *const) $21 = 0x0000000100002188
    
    // 打印属性列表信息
    (lldb) p *$21
    (property_list_t) $22 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1
        first = (name = "age", attributes = "Ti,N,V_age")
      }
    }
    

    这里我们看到了age属性,在class_rw_t中,我们却没找到成员变量的存储位置,但是class_rw_t中,有获取和设置class_ro_t的方法,我们来看一下class_ro_t结构体。

    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;
    
        省略。。。
    };
    

    我们看到class_ro_t中有const ivar_list_t * ivars,我们来打印一下:

    (lldb) p $3.ro()
    (const class_ro_t *) $23 = 0x0000000100002090
    
    // 打印 class_ro_t 内容
    (lldb) p *$23
    (const class_ro_t) $24 = {
      flags = 388
      instanceStart = 8
      instanceSize = 20
      reserved = 0
      ivarLayout = 0x0000000100000f28 "\x01"
      name = 0x0000000100000f1e "PYTeacher"
      baseMethodList = 0x00000001000020d8
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000100002140
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000100002188
      _swiftMetadataInitializer_NEVER_USE = {}
    }
    
    // 打印 ivar_list_t 地址
    (lldb) p $24->ivars
    (const ivar_list_t *const) $25 = 0x0000000100002140
      Fix-it applied, fixed expression was: 
        $24.ivars
    
    // 打印 ivar_list_t 内容
    (lldb) p *$25
    (const ivar_list_t) $26 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x00000001000022c8
          name = 0x0000000100000f4a "name"
          type = 0x0000000100000f7c "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    
    // 获取成员变量
    (lldb) p $26.get(0)
    (ivar_t) $27 = {
      offset = 0x00000001000022c8
      name = 0x0000000100000f4a "name"
      type = 0x0000000100000f7c "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
    
    (lldb) p $26.get(1)
    (ivar_t) $28 = {
      offset = 0x00000001000022d0
      name = 0x0000000100000f4f "_age"
      type = 0x0000000100000f88 "i"
      alignment_raw = 2
      size = 4
    }
    

    ivar_list_t中存储这name_age成员变量。

    小结

    1. 对象的成员变量存储在class_ro_t中;
    2. 对象方法属性存储在class_rw_t中;
    3. 类方法存储在元类class_rw_t中。

    相关文章

      网友评论

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

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