美文网首页
OC类结构分析

OC类结构分析

作者: 爱你因为泰勒 | 来源:发表于2020-07-28 18:07 被阅读0次

    类Class ,也可以称为类对象,在编译时会转成objc_class, objc_class继承自objc_object,objc_object是结构体:

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    

    objc_class也是一个结构体,以下是类的结构:

    struct objc_class : objc_object {
        // Class ISA; // 8字节(指针是 8字节)  ISA是默认父类中有
        Class superclass; // 8字节
        cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
        void setData(class_rw_t *newData) {
            bits.setData(newData);
        }
    

    以下是缓存类 cache_t 的结构:

    struct cache_t {
        struct bucket_t *_buckets; // 8字节
        mask_t _mask;  // 4字节
        mask_t _occupied; // 4字节
    

    如果我们在类中添加了属性 和 方法的话,那他们具体在哪个地方存储着呢?
    显然,我们在类的结构体中可以看到属性 和 方法是存放在 class_data_bits_t类型的 bites中。具体如何确认呢?

    可以确认在 objc_class结构体中,isa是指针占用 8字节,superclass同样也是8字节,缓存cache是16字节,这样,我们可以通过内存偏移找到bits的内存地址

    如下,创建一个类,添加成员变量hobby以及属性nickName,添加实例方法和类方法:

    @interface LGPerson : NSObject{
        NSString *hobby; //成员变量
    }
    
    @property (nonatomic, copy) NSString *nickName; //属性
    
    - (void)sayHello;
    + (void)sayHappy;
    
    @end
    

    然后创建 初始化

    LGPerson *person = [LGPerson alloc];
    Class pClass      = object_getClass(person);
    NSLog(@"%@ - %p",person,pClass);
    

    查找流程如下:
    我们先打个断点,然后通过lldb命令来查找对应的属性和方法 。

    (lldb) p/x pClass //查找指针地址,目前找到的是isa的指针地址
    (Class) $21 = 0x00000001000023b0 LGPerson
    

    通过内存偏移 32 ( isa 8 ,superClass 8,cache是结构体,但是里面占用了16个字节 ),0x00000001000023b0 + 32 ,对应的16进制为 0x00000001000023d0

    (lldb) p (class_data_bits_t *)0x00000001000023d0  
    (class_data_bits_t *) $23 = 0x00000001000023d0
    (lldb) p $23->data()    //这里是调用 class_rw_t *data() { return bits.data(); }方法得到bits里面的东西
    (class_rw_t *) $24 = 0x000000010194f560
    (lldb) p $24->ro  
    (const class_ro_t *) $26 = 0x0000000100002308
    (lldb) p *$26  //打印出里面的值
    (const class_ro_t) $27 = {
      flags = 388
      instanceStart = 8
      instanceSize = 24
      reserved = 0
      ivarLayout = 0x0000000100001f89 "\x02"
      name = 0x0000000100001f80 "LGPerson"
      baseMethodList = 0x0000000100002240
      baseProtocols = 0x0000000000000000
      ivars = 0x00000001000022a8
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x00000001000022f0
    } //这里面可以看到baseProperties ,属性便可以在里面找到
    (lldb) p *$27.baseProperties
    (property_list_t) $29 = {
      entsize_list_tt<property_t, property_list_t, 0> = {
        entsizeAndFlags = 16
        count = 1
        first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
      }
    }
    
    //对于成员变量  在ivars中可以找到
    (lldb) p $27.ivars 
    (const ivar_list_t *const) $30 = 0x00000001000022a8
    (lldb) p *$30
    (const ivar_list_t) $31 = {
      entsize_list_tt<ivar_t, ivar_list_t, 0> = {
        entsizeAndFlags = 32
        count = 2
        first = {
          offset = 0x0000000100002378
          name = 0x0000000100001e64 "hobby"
          type = 0x0000000100001fa6 "@\"NSString\""
          alignment_raw = 3
          size = 8
        }
      }
    }
    //同时,属性也会对应生成一个成员变量在ivars中
    (lldb) p $30.get(1)
    (ivar_t) $32 = {
      offset = 0x0000000100002380
      name = 0x0000000100001e6a "_nickName"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
      Fix-it applied, fixed expression was: 
        $30->get(1)
    

    可以肯定的是属性在 class_data_bits_t bits 里面,而方法呢?同样在ivars中可以找到方法,如下

    (lldb) p $27.baseMethodList     //同样在ivars中可以找到方法
    (method_list_t *const) $33 = 0x0000000100002240
    (lldb) p *$33
    (method_list_t) $34 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 4
        first = {
          name = "sayHello"
          types = 0x0000000100001f8b "v16@0:8"
          imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
        }
      }
    }
    

    以上片段中可以看到方法列表中 有一个 count = 4,那么是有具体哪几个方法呢?

    (lldb) p $34.get(1)
    (method_t) $35 = {
      name = "nickName"  ///get方法
      types = 0x0000000100001f93 "@16@0:8"
      imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
    }
    (lldb) p $34.get(2)
    (method_t) $36 = {
      name = "setNickName:"  ///set方法
      types = 0x0000000100001f9b "v24@0:8@16"
      imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
    }
    (lldb) p $34.get(3)
    (method_t) $37 = {
      name = ".cxx_destruct"  //C++的系统默认方法
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
    }
    (lldb) p $34.get(0)
    (method_t) $38 = {
      name = "sayHello"  //实例方法
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
    

    我们创建的类中是应该还有一个 happy类方法的,但是没有在baseMethodList中找到,那会在哪儿呢?同样的操作,再进行一遍,只是中间多了一个元类

    (lldb) x/4gx pClass
    0x1000023b0: 0x001d800100002389 0x0000000100afe140
    0x1000023c0: 0x00000001003a1280 0x0000000000000000
    (lldb) p/x 0x001d800100002389 & 0x00007ffffffffff8   //找到元类
    (long) $40 = 0x0000000100002388
    (lldb) x/4gx 0x0000000100002388
    0x100002388: 0x001d800100afe0f1 0x0000000100afe0f0
    0x100002398: 0x0000000101e24ae0 0x0000000100000007
    (lldb) p (class_data_bits_t *)0x1000023a8  //内存偏移到 bits 部分
    (class_data_bits_t *) $41 = 0x00000001000023a8
    (lldb) p $41->data()
    (class_rw_t *) $42 = 0x000000010194f520
    (lldb) p $42->ro
    (const class_ro_t *) $43 = 0x00000001000021f8
    (lldb) p *$43
    (const class_ro_t) $44 = {
      flags = 389
      instanceStart = 40
      instanceSize = 40
      reserved = 0
      ivarLayout = 0x0000000000000000
      name = 0x0000000100001f80 "LGPerson"
      baseMethodList = 0x00000001000021d8
      baseProtocols = 0x0000000000000000
      ivars = 0x0000000000000000
      weakIvarLayout = 0x0000000000000000
      baseProperties = 0x0000000000000000
    }
    (lldb) p $44.baseMethodList
    (method_list_t *const) $45 = 0x00000001000021d8
    (lldb) p *$45
    (method_list_t) $46 = {
      entsize_list_tt<method_t, method_list_t, 3> = {
        entsizeAndFlags = 26
        count = 1
        first = {
          name = "sayHappy"
          types = 0x0000000100001f8b "v16@0:8"
          imp = 0x0000000100001bc0 (LGTest`+[LGPerson sayHappy] at LGPerson.m:17)
        }
      }
    }
    

    以上,可以找到类中的类方法。由此可知,我们可以总结出:
    1、类(Class)最终是会编译成 objc_class(继承自objc_object).
    2、objc_class结构体中含有(默认的)ISA、(父类)superclass、(缓存)cache、(存储属性和方法)bits
    3、在(class_data_bits_t)bits中,类中的属性会在 ro 的 baseProperties中,而对应生成的成员变量会在 ro 的 ivars中。
    4、在(class_data_bits_t)bits中,类中的方法会在 ro 的 baseMethodList中,而类中的类方法会在元类中的 ro 的 baseMethodList中。

    中间来个插曲: 为什么在外面isa是Class呢,因为创建初始化isa时就强转为Class类型

    相关文章

      网友评论

          本文标题:OC类结构分析

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