Day 1 · Objective-C Runtime

对象模型与基础消息查找

这是我当前从 Runtime 开始复习的落点:先把实例、类对象、元类,以及 isasuperclass 放到同一张图里;不追求一次覆盖全部 Runtime。

01

Runtime 不是一组 API,而是 Objective-C 动态性的运行时系统

编译器把类、方法、协议和属性等信息编译为元数据。App 加载时,Runtime 注册这些类,建立对象、类对象、元类与继承关系,并负责对象创建、消息派发、反射和部分动态修改能力。

objc/runtime.h 是观察这套模型的公开窗口,例如 object_getClassclass_getSuperclassclass_copyIvarList;它不等于 Runtime 的全部。

02

实例对象保存状态;类对象共享规则;元类承载类方法

对象解决的问题保存或关联的信息
实例对象同一个类下,每个对象有不同状态isa 信息与每个实例自己的 ivar 具体值
类对象同类实例共同遵守什么规则实例布局描述、实例方法、属性/协议元数据、方法缓存和继承关系
元类对象类对象收到类方法消息时从哪里查找类方法的缓存、方法数据与元类继承关系

同一个类的两个实例通常共享同一份实例方法实现;它们调用同一个方法得到不同结果,通常是因为 ivar 值不同,而不是每个实例保存了不同的方法列表。

ivar 不等于 property。ivar(实例变量)是实例对象内存中的真实存储;property 是 getter/setter 的声明与访问语义,通常由 ivar 支撑,但也可以是计算属性或类属性。

03

方法在 Runtime 中由 SEL、Method 和 IMP 连接

SEL找什么

消息名/选择子,例如 movesetName:

Method当前类中找到的记录

关联 selector、类型编码和实现。

IMP最终执行什么

方法实现的函数指针。

同一个 SEL(eat) 可以在 LCAnimalLCDog 的不同类对象中对应不同的 Method 和 IMP。接收者的 isa 决定从哪个类开始找,因此父类和子类可以命中不同实现。

04

isa 决定起点;superclass 决定未命中后的方向

dog 实例
  │ isa
  ▼
LCDog 类对象 ──superclass──> LCAnimal 类对象 ──superclass──> NSObject 类对象
  │ isa                          │ isa                            │ isa
  ▼                              ▼                                ▼
LCDog 元类 ──superclass──> LCAnimal 元类 ──superclass──> NSObject 元类

isa 回答“我是哪个类的实例”。Runtime 复用这个答案,把对应类对象作为消息查找起点;真正遍历 cache、方法数据和 superclass 链的是消息派发逻辑。

经典 Runtime 中,isa 可以近似理解为对象头里指向类对象的 Class 指针。现代 Runtime 可能使用非指针 isa 等位域优化,因此业务代码应使用公开 API 观察关系,不直接依赖内部内存表示。

05

找父类实现,不会改变消息接收者

[dog move] 为例:接收者是 dog 实例。Runtime 从 dog.isa 得到 LCDog 类对象,概念上先看该类上下文的 method cache,再查看当前类直接声明的方法数据;若未命中,沿普通类对象的 superclass 链继续到父类。

dog 实例
  │ isa
  ▼
LCDog 类对象:未实现 -move
  │ superclass
  ▼
LCAnimal 类对象:命中 -move 的 Method → IMP
  │
  ▼
以 self = dog、_cmd = move 执行

即使最终执行的是 LCAnimal-moveself 仍是原来的 dog,即 LCDog 实例。去父类找实现,不等于把对象转换成父类对象。

类消息同理:[LCDog origin] 的接收者是 LCDog 类对象;第一站是该类对象的 isa 指向的 LCDog 元类,再沿元类的 superclass 链查找。

06

用公开 API 验证,不靠猜内部结构

普通对象上,下面两条通常都得到 LCDog 类对象;但它们的语义不同。

LCDog *dog = [LCDog new];

[dog class];                    // class 消息:对象对外报告的类
object_getClass(dog);           // Runtime 视角下对象所属的类

[LCDog class];                  // LCDog 类对象
object_getClass([LCDog class]); // LCDog 元类对象

[obj class] 是消息语义,可能被覆盖或调整;object_getClass(obj) 更适合观察 Runtime 关系。典型边界是 KVO:实际 Runtime 类可能变成动态子类,而 -class 仍可能对外报告原始类。

闭卷复习

为下一次口述留一个起点

完成这次口述后,给自己留一个回看的时间。状态只存在当前浏览器,不上传,也不替你判断掌握程度。

还没有留下一次回看的时间。

实例对象、类对象、元类分别解决什么问题?

实例对象保存每个对象自己的 ivar 值;类对象描述实例共享的布局、实例方法和继承关系;元类让类对象可以接收类方法消息,并承载类方法的元数据。

为什么实例对象不需要保存方法列表?

同类实例共享方法元数据与实现,而各自保存不同状态。把方法列表复制到每个实例会造成冗余,也不利于表达统一的继承和缓存关系。

[dog move] 执行父类实现时,self 是谁?

仍是原始接收者 dogsuperclass 只改变查找实现的位置,不会改变接收者或创建父类实例。

object_getClass([LCDog class]) 得到什么?

在普通 class 语义下,得到 LCDog 元类对象。因为 LCDog 类对象本身也是对象,是该元类的实例。

下一段

等这页讲稳,再决定往哪里走

如果普通方法查找已经能不看答案讲清楚,下一次我会从消息派发和消息转发继续;如果还不稳,就留在这一页继续修正。学习顺序不应该被预设课程表反过来绑架。

返回专题索引