对于OC的实例方法,用法是先实例化对象,再用实例对象调用方法:
1  | // ObjectA.h  | 
和成员变量的使用方式一样,都是先初始化对象,再使用。这就容易让人产生一个误区:实例方法和成员变量一样,每个对象独一份,在对象初始化时存储在堆区。
事实上,实例方法和成员变量有着本质的区别,成员变量是在对象实例化之后,存储在内存的堆区(即对象所在的地址);而实例方法作为可执行部分,编译之后存储在代码段,实例方法的地址(IMP)、方法名等信息则被存储在类对象中。
类对象的定义:
1  | struct class_rw_ext_t {  | 
用一个图大概描述一下其内存布局:
用lldb大致验证一下:
初始化对象:
1  | ObjectA *aObj = [[ObjectA alloc] init];  | 
获取对象地址:
1  | (lldb) po aObj  | 
获取isa指针:
1  | (lldb) x/1gx 0x282dccdd0  | 
获取bits:
1  | (lldb) x/5gx 0x00000001001f14a8  | 
从bits值中获取class_rw_t结构体:
1  | (lldb) p/x 0x8000000282fea4e4 & 0x0f00007ffffffff8UL  | 
获取class_ro_t结构体:
1  | (lldb) x/2gx 0x0000000282fea4e0  | 
获取baseMethods
1  | (lldb) x/5gx 0x00000001001f02c8  | 
查看baseMethods的内容:
1  | // baseMethods为struct method_list_t指针。  | 
乍一看,似乎这里并没有存储任何和方法列表相关的信息,但是细看结构体模版entsize_list_tt的getOrEnd实现就会发现,该模版在结构体内部维护了一个Element类型的数组,其实际结构可以理解为:
1  | struct entsize_list_tt {  | 
从上面可以看出,该模版作为method_list_t的实现,Element的类型为:method_t。
1  | struct method_t {  | 
到这里,baseMethods的内容便呼之欲出了:
1  | (lldb) x/15gx 0x00000001001f0238 // 以指针的形式读取15条baseMethods的内容  | 
用lldb将方法列表的存储位置过一遍后,整个过程清晰了很多,接下来再用代码走一遍:
1  | // 定义 ISA_MASK 用于获取isa指针  |