第九章:灵魂 —— Runtime 运行时

9.1 面向对象的谎言

你写的代码: [receiver message]

编译器眼中的代码: objc_msgSend(receiver, @selector(message))

Objective-C 是一门披着 C 语言外衣的动态语言。 普通的 C 语言在编译时就决定了哪个函数被调用(静态绑定)。 而 OC 直到运行的那一刻,才决定去哪里找这个方法。

这就像是: C 语言是拿着地图走路,路线早就画好了。 OC 是打车,上车了才跟司机说去哪,半路还能改目的地(Method Swizzling)。

9.2 objc_msgSend 的魔法

这个函数是 OC 的心脏。它是用汇编写的,为了极致的速度。 它做的事情很简单:

  1. 检查 receiver 是不是 nil。是的话直接返回 0。
  2. receiverClass 的缓存(Cache)里找 message
  3. 找不到?去 Class 的方法列表(Method List)里找。
  4. 还找不到?去 SuperClass 里找。
  5. …一直找到 NSObject

这也是为什么 OC 的方法调用比 C++ 的虚函数稍微慢那么一点点的原因,但换来的是无与伦比的灵活性。

9.3 黑魔法:Method Swizzling

因为方法查找本身就是一个在表里查字符串的过程。 那如果我们悄悄把表改了呢? 我们要把 [UIViewController viewWillAppear:] 方法换成我们自己的 [MyClass my_viewWillAppear:],在里面偷偷埋点统计用户行为,然后再调用回原来的方法。

这就是 AOP(面向切面编程) 在 OC 里的终极形态。 Swift 只有在继承了 NSObject 并且标记了 dynamic 的时候才能这么干。但在 OC 里,这是标配。

你可以把系统的 UIImage imageNamed: 替换掉,给所有图片加载加个缓存或者是打印个日志。 能力越强,责任越大。乱用 Swizzling 的后果就是 Debug 的时候你想杀人。

9.4 消息转发:死前的挣扎

obj 真的找不到 message 时,系统不会立刻崩溃,而是给你三次机会:

  1. Resolve: +resolveInstanceMethod: —— 给你个机会动态添加一个方法。
  2. ForwardingTarget: -forwardingTargetForSelector: —— 问你有没有备胎(其他对象)能处理这个消息?
  3. ForwardInvocation: -forwardInvocation: —— 把消息打包成 NSInvocation,随你随便怎么处理(改参数、改目标、甚至吞掉)。

如果这三步你都无动于衷,那么 doesNotRecognizeSelector: 才会把你送走。

9.5 总结

Runtime 是 OC 的灵魂。 Swift 很棒,很快,很安全。 但只有在操作 Runtime 的时候,你才会感觉自己像个真正的黑客。

使用 Hugo 构建
主题 StackJimmy 设计