对于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指针 |