类结构初探

456 阅读5分钟
一.探索前需知 

 1.元类的概念 元类是系统在编译期帮我们创建的,里面的结构和我们自己创建类结构是一摸一样的,元类和类之间的纽带是通过isa来链接的 

 2.类和元类之间的关系以及继承 下面我们用一幅图来表示它们之间的关系:

二.类的本质

1.类到底是什么,类的底层是怎么实现的?

一般我们想看系统底层实现,一般有两个途径:第一是LLVM源码分析 第二个就是clang-rewrite-objc 进行底层编译,现在我们用第二种方式进行探究:首先我们先创建一个ZYPeople 类 里面有一些方法和属性以及实例变量:


然后打开终端 输入命令:clang -rewrite-objc ZYPeople.m -o LGPeople.cpp(输出个.cpp编译后的文件)

现在文件夹里多了个文件:LGPeople.cpp



然后我们打开文件进行分析(文件编译后的代码太大,建议从后往前看):


里面method_list 里有 sayHello、sayHappy的方法.


在这里有IVARS成员变量属性 hobby. 但是这些东西是怎么出来的呢?下一步我们继续探究

三.类的本质深入

1.首先我们在网上 http://www.opensource.apple.com/apsl/里下一份objc的源码通过一些配置导入我们的工程.里面

 LGPerson *person = [LGPerson alloc];Class pClass = object_getClass(person);

点击这个Class ->

struct objc_class : objc_object {

    // Class ISA; // 8

    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();

    }

我们发现类其实是继承objc_object里的结构体.结构体里有 superclass、cache、bits 这几个属性其实 objc_object里还有个isa 属性、其中isa和superclass在存储空间里占8字节、cache是个结构体一共是16个字节.等于我们把类的首地址也就是ISA的地址偏移 32位就是bits所在的地址,下面我们就用lldb开始分析:

我们看到pClass的内存结构 其中 0x1000023b0 代表着isa的地址,我们现在偏移32位 也就是 0x1000023d0 看一看里面的内容,也就是 



在这里我们看到一些熟悉的东西methods、properties、protocols.

然后我们再把methods里的东西用lldb 展示出来:

发现list里面有sayHello的方法.在这里肯定有很多人觉得方法、属性、协议分别存在methods,protocols,porperties 里. (其实这是个误区,真正其实是放在ro里的在运行时阶段会copy到rw_t里,所以我们会在这里也能看到)

下面我们着重研究下ro.


看下baseMethodList的结构:

(lldb) p $16.baseMethodList(method_list_t *const) $17 = 0x0000000100002240

(lldb) p *$17(method_list_t) $18 = { 

 entsize_list_tt<method_t, 

method_list_t, 3> = 

 entsizeAndFlags = 26 

 count = 4 

 first = 

{ name = "sayHello" types = 0x0000000100001f87 "v16@0:8" imp = 0x0000000100001ba0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13) } }

(lldb) p $17.get(0)
(method_t) $29 = {
  name = "sayHello"
  types = 0x0000000100001f87 "v16@0:8"
  imp = 0x0000000100001ba0 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
}
  Fix-it applied, fixed expression was: 
    $17->get(0)
(lldb)  p $17.get(1)
(method_t) $30 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f87 "v16@0:8"
  imp = 0x0000000100001c70 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
  Fix-it applied, fixed expression was: 
    $17->get(1)

我们发现sayHello方法会在ro的baseMethodList里面 同样hobby属性 会在ro的ivars里,属性会在ro的baseProperties 里 验证如下:

(lldb) p $16.ivars  (const ivar_list_t *const) $19 = 0x00000001000022a8

(lldb) p *$19(const ivar_list_t) $20 = { entsize_list_tt<ivar_t, ivar_list_t, 0> = { entsizeAndFlags = 32 count = 2 first = { offset = 0x0000000100002378 name = 0x0000000100001e6c "hobby" type = 0x0000000100001fa2 "@\"NSString\"" alignment_raw = 3 size = 8 } } }

(lldb) p $20.get(0)
(ivar_t) $27 = {
  offset = 0x0000000100002378
  name = 0x0000000100001e6c "hobby"
  type = 0x0000000100001fa2 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $20.get(1)
(ivar_t) $28 = {
  offset = 0x0000000100002380
  name = 0x0000000100001e72 "_name"
  type = 0x0000000100001fa2 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

发现属性在底层会默认生成个带_ 的成员变量(_name)

(lldb) p $16.baseProperties

(property_list_t *const) $25 = 0x00000001000022f0

(lldb) p *$25

(property_list_t) $26 = { entsize_list_tt<property_t, property_list_t, 0> = { entsizeAndFlags = 16 count = 1 first = (name = "name", attributes = "T@\"NSString\",C,N,V_name") } }

所有的一切一切都是那么的请切且自然但是呢还有个问题之前类里面写的类方法 sayHappy 去哪里了呢?之前所展示的方法只是对象方法,其实呢这就要回到文章的开头,类的ISA指向了元类,所以呢类方法归元类去管理,相当于元类里的实例方法,那我们就要得到元类,找到元类去研究 、首先通过 类的 isa &  0x00007ffffffffff8 得到元类的地址、再通过元类的 x/4gx 元类地址 得到元类的ISA偏移32位找到class_data_bits_t 之后的操作就和上面所述的一模一样 有兴趣的盆友可以一起玩下,最后可以看到 sayHappy 就在元类的ro的baseMethodList里面.

总结

 经过探索发现 OC的 类本质上是objc_class 的一个结构体 存储了 superclass cache bits 方法列表 属性列表 变量列表 都存在了 class_rw_t 的结构体中,其中类的成员变量在ivars中 属性在baseProperties 但是在ivars 中也是可以找到带 _ 的属性变量 实例方法 在baseMethodList 中找到 类方法 就在元类中。