说 Objective-C 是一门“运行时语言”(Runtime-based Language),本质上是因为它把决策权从编译阶段推迟到了执行阶段。
在 C++ 或 Java 等语言中,很多事情在代码写完、编译器跑完的那一刻就“写死”了;而在 Objective-C 里,编译器只是个“传话筒”,真正的表演要在程序运行起来后才开始。
以下是支撑这一称呼的三个核心支柱:
1. 动态绑定 (Dynamic Binding)
在传统的静态语言中,调用一个方法就像是拨打一个分机号,线路在编译时就插好了。但在 Objective-C 中,调用方法是发送消息。
当你写下 [obj doSomething] 时:
- 编译期: 编译器只是简单地将其转换为
objc_msgSend(obj, @selector(doSomething))。它并不确定obj是否真的有这个方法,只要它看到声明了,就放行。 - 运行时: 只有程序跑到了这一行,系统才会通过
obj的isa指针找到它的类,然后在类的方法列表中查找doSomething。
这意味着你可以在运行时动态地更换 obj 指向的对象类型,或者给类动态增加方法,程序依然能跑通。
2. 消息转发机制 (Message Forwarding)
如果一个对象收到了它无法响应的消息,在其他语言中程序会立即崩溃。但 Objective-C 的运行时给了对象三次自救机会:
- 动态方法解析: 运行时问你:“要不要临时加个方法实现?”
- 备援接收者: 运行时问你:“你处理不了,要不要把这活儿转交给别的对象做?”
- 完全消息转发: 运行时把消息包装成
NSInvocation对象,让你像处理数据一样处理这个“动作”。
这种“边跑边商量”的特性,是 Objective-C 能够实现类似 AOP(面向切面编程) 或 热修复(Hotfix) 的底层基础。
3. 内省与反射 (Introspection & Reflection)
Objective-C 具有极其强大的自我察觉能力。在运行时,程序可以“询问”对象:
isKindOfClass:你是不是某个类或其子类的实例?respondsToSelector:你能不能响应这个方法?conformsToProtocol:你有没有遵守这个协议?
甚至,你可以通过 Method Swizzling(黑魔法),在运行时交换两个方法的具体实现(IMP)。这在不修改原有类源代码的情况下,能极大地改变程序的行为。
4. Runtime 库:幕后黑手
这一切的背后,是一个用 C 和汇编编写的动态库——Objective-C Runtime。
所有的 Objective-C 对象在底层都是 C 语言的结构体:
因为有了这个 isa 指针,每个对象在运行时都能找到自己的身份证明(类对象)。类对象里记录了方法列表、属性列表和协议列表。
总结
“运行时语言”意味着:
- 灵活性: 可以在程序运行期间修改类结构、交换方法、动态添加属性。
- 弱化编译约束: 编译器不再是绝对的权威,它更像是一个建议者。
- 高度动态的 UI 交互: 这也是为什么早期的 iOS 开发中,Delegate、Target-Action 等模式如此盛行的原因,因为它们高度依赖这种动态分发。