3-13.【OC】【Runtime】如果一个方法在子类和父类中都存在,cache 是如何区分的?

0 阅读2分钟

Cache 是属于“类对象”的私有资产,而不是全局共享的。

Runtime 区分它们并不是靠某个特殊的 ID,而是靠存储的位置


1. “私有笔记本”原则

在 Objective-C 中,每一个类(Subclass)和它的父类(Superclass)在内存中都是独立的类对象(Class Object)

  • 每个类对象都有自己独立的 cache_t 结构。
  • 你可以把 cache 想象成每个类随身携带的一个私有笔记本

2. 查找逻辑:谁收到了消息,就先看谁的笔记本

当你调用 [subObject method] 时,objc_msgSend 的逻辑如下:

  1. 确定 Receiver 的类:发现 subObject 的类是 Subclass
  2. 查找 Subclass 的 Cache:只会在 Subclass 的私有笔记本里找。
  3. 如果命中:直接执行。

关键点来了:

  • 如果子类重写(Override)了方法:慢速路径查找时会先在子类的方法列表里找到子类的 IMP,然后存入子类的 Cache
  • 如果子类没重写:慢速路径会顺着父类链找,最终在父类里找到 IMP,但 Runtime 依然会把这个父类的 IMP 存入子类的 Cache

结论:Cache 永远是为“接收者(Receiver)”服务的。即便方法是在父类实现的,一旦被子类调用,它也会在子类的 Cache 里存一份副本,以便下次调用时不用再去爬父类链。


3. 如果两个类都调用了同一个父类方法?

假设 SubclassASubclassB 都继承自 BaseClass,且都没有重写 init 方法:

  • 当你调用 [[SubclassA alloc] init] 时,BaseClassinit 地址会被缓存在 SubclassA 的 Cache 中。
  • 当你调用 [[SubclassB alloc] init] 时,BaseClassinit 地址也会被缓存在 SubclassB 的 Cache 中。

它们互不干扰,物理隔离


4. 深度细节:Cache 冲突与重写

如果一个方法在子类和父类中都存在,Cache 的内容取决于消息发送的目标

调用场景查找位置缓存结果 (IMP)
[SubObject hello] (子类重写)Subclass 的 Cache指向 子类实现
[SuperObject hello]Superclass 的 Cache指向 父类实现
[SubObject hello] (子类未重写)Subclass 的 Cache指向 父类实现

5. 为什么不合并 Cache 以节省内存?

你可能会想,如果大家都用父类的方法,为什么要存这么多份?

  1. 性能极速化:如果去查共享 Cache,可能需要加锁或处理复杂的命名空间,而 objc_msgSend 追求的是 0.1 纳秒级的响应。
  2. 动态性:OC 支持在运行时动态修改某个类的方法(Swizzling)。如果 Cache 是共享的,修改一个子类的方法可能会意外影响到父类或其他子类,这会造成灾难性的 Bug。

总结

Cache 区分它们的方法非常简单粗暴:每个类一个坑

调用者是谁,就去谁的坑里找;找到了就用,找不到就去父类搜,搜到了再回填到自己的坑里。