iOS类的本质

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
2
3
4
#define FAST_IS_SWIFT           (1UL<<0)
#define FAST_HAS_DEFAULT_RR (1UL<<1)
#define FAST_REQUIRES_RAW_ISA (1UL<<2)
#define FAST_DATA_MASK 0x00007ffffffffff8UL

在编译期间类的结构中的 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
2
3
4
5
6
7
8
if (!cls->isRealized()) {
rwlock_writer_t lock(runtimeLock);
realizeClass(cls);
}

if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
}

参考 对象是如何初始化的

@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 使用了汇编语言来编写。

看着看着来到了这里

希望对您有所帮助,您的支持将是我莫大的动力!