Day 1 · Objective-C Runtime
对象模型与基础消息查找
这是我当前从 Runtime 开始复习的落点:先把实例、类对象、元类,以及 isa、superclass 放到同一张图里;不追求一次覆盖全部 Runtime。
01
Runtime 不是一组 API,而是 Objective-C 动态性的运行时系统
编译器把类、方法、协议和属性等信息编译为元数据。App 加载时,Runtime 注册这些类,建立对象、类对象、元类与继承关系,并负责对象创建、消息派发、反射和部分动态修改能力。
objc/runtime.h 是观察这套模型的公开窗口,例如 object_getClass、class_getSuperclass、class_copyIvarList;它不等于 Runtime 的全部。
02
实例对象保存状态;类对象共享规则;元类承载类方法
| 对象 | 解决的问题 | 保存或关联的信息 |
|---|---|---|
| 实例对象 | 同一个类下,每个对象有不同状态 | isa 信息与每个实例自己的 ivar 具体值 |
| 类对象 | 同类实例共同遵守什么规则 | 实例布局描述、实例方法、属性/协议元数据、方法缓存和继承关系 |
| 元类对象 | 类对象收到类方法消息时从哪里查找 | 类方法的缓存、方法数据与元类继承关系 |
同一个类的两个实例通常共享同一份实例方法实现;它们调用同一个方法得到不同结果,通常是因为 ivar 值不同,而不是每个实例保存了不同的方法列表。
ivar 不等于 property。ivar(实例变量)是实例对象内存中的真实存储;property 是 getter/setter 的声明与访问语义,通常由 ivar 支撑,但也可以是计算属性或类属性。
03
方法在 Runtime 中由 SEL、Method 和 IMP 连接
消息名/选择子,例如 move、setName:。
关联 selector、类型编码和实现。
方法实现的函数指针。
同一个 SEL(eat) 可以在 LCAnimal 与 LCDog 的不同类对象中对应不同的 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 的 -move,self 仍是原来的 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 是谁?
仍是原始接收者 dog。superclass 只改变查找实现的位置,不会改变接收者或创建父类实例。
object_getClass([LCDog class]) 得到什么?
在普通 class 语义下,得到 LCDog 元类对象。因为 LCDog 类对象本身也是对象,是该元类的实例。
下一段
等这页讲稳,再决定往哪里走
如果普通方法查找已经能不看答案讲清楚,下一次我会从消息派发和消息转发继续;如果还不稳,就留在这一页继续修正。学习顺序不应该被预设课程表反过来绑架。
返回专题索引