class
class_data_bits_t bits 这是一个64位结构体,里面只有一个 uintptr_t bits,
在x86_64架构上,并且class_rw_t [2, 47]只占到了47位,如下
63 | 47 | 2 | 1 | 0 |
---|---|---|---|---|
extra | class_rw_t data | requires raw_isa | has default_rr | is_swift |
1 |
在编译期间类的结构中的 class_data_bits_t *data
指向的是一个 class_ro_t *
指针:
- 类在内存中的位置是在编译期间决定的,在之后修改代码,也不会改变内存中的位置。
- 类的方法、属性以及协议在编译期间存放到了“错误”的位置,直到 realizeClass 执行之后,才放到了 class_rw_t 指向的只读区域 class_ro_t,这样我们即可以在运行时为 class_rw_t 添加方法,也不会影响类的只读结构。
- 在 class_ro_t 中的属性在运行期间就不能改变了,再添加方法时,会修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,对于方法的添加会在之后的文章中分析。
在 Objective-C 运行时 初始化的过程中会对其中的类进行第一次初始化也就是执行 realizeClass 方法,为类分配可读写结构体 class_rw_t 的空间,并返回正确的类结构体。
而 _class_initialize 方法会调用类的 initialize 方法,
1 | if (!cls->isRealized()) { |
参考 对象是如何初始化的
@selector()
使用 @selector() 生成的选择子不会因为类的不同而改变,其内存地址在编译期间就已经确定了。也就是说向不同的类发送相同的消息时,其生成的选择子是完全相同的。
- Objective-C 为我们维护了一个巨大的选择子表
- 在使用 @selector() 时会从这个选择子表中根据选择子的名字查找对应的 SEL。如果没有找到,则会生成一个 SEL 并添加到表中
- 在编译期间会扫描全部的头文件和实现文件将其中的方法以及使用 @selector() 生成的选择子加入到选择子表中
objc_msgsend()
Objective-C 中 objc_msgSend 的实现并没有开源,它只存在于 message.h 这个头文件中。
当编译器遇到一个方法调用时,它会将方法的调用翻译成以下函数中的一个 objc_msgSend、objc_msgSend_stret、objc_msgSendSuper 和 objc_msgSendSuper_stret。 发送给对象的父类的消息会使用 objc_msgSendSuper 有数据结构作为返回值的方法会使用 objc_msgSendSuper_stret 或 objc_msgSend_stret 其它的消息都是使用 objc_msgSend 发送的
objc_msgSend -> _class_loopupMethodAndLocadCache3 -> lookUpImpOrForward
对于 objc/objc-runtime-new.mm 中 lookupImpOrForward 的总结如下:
- 无锁的缓存查找
- 如果类没有实现(isRealized)或者初始化(isInitialized),实现或者初始化类
- 加锁
- 缓存以及当前类中方法的查找
- 尝试查找父类的缓存以及方法列表
- 没有找到实现,尝试方法解析器
- 进行消息转发
- 解锁、返回实现
不过因为 _class_lookupMethodAndLoadCache3 传入的 cache = NO,所以这里会直接跳过 if 中代码的执行,在 objc_msgSend 中已经使用汇编代码查找过了。
如果缓存没有命中 -> 找到之后加入缓存 -> 第二次调用的时候,就不会走lookupImpOrForward,而是汇编查找了缓存,
为了提高消息传递的效率,ObjC 对 objc_msgSend 以及 cache_getImp 使用了汇编语言来编写。
看着看着来到了这里