9.1 面向对象的谎言
你写的代码:
[receiver message]
编译器眼中的代码:
objc_msgSend(receiver, @selector(message))
Objective-C 是一门披着 C 语言外衣的动态语言。 普通的 C 语言在编译时就决定了哪个函数被调用(静态绑定)。 而 OC 直到运行的那一刻,才决定去哪里找这个方法。
这就像是: C 语言是拿着地图走路,路线早就画好了。 OC 是打车,上车了才跟司机说去哪,半路还能改目的地(Method Swizzling)。
9.2 objc_msgSend 的魔法
这个函数是 OC 的心脏。它是用汇编写的,为了极致的速度。 它做的事情很简单:
- 检查
receiver是不是nil。是的话直接返回 0。 - 在
receiver的Class的缓存(Cache)里找message。 - 找不到?去
Class的方法列表(Method List)里找。 - 还找不到?去
SuperClass里找。 - …一直找到
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 时,系统不会立刻崩溃,而是给你三次机会:
- Resolve:
+resolveInstanceMethod:—— 给你个机会动态添加一个方法。 - ForwardingTarget:
-forwardingTargetForSelector:—— 问你有没有备胎(其他对象)能处理这个消息? - ForwardInvocation:
-forwardInvocation:—— 把消息打包成NSInvocation,随你随便怎么处理(改参数、改目标、甚至吞掉)。
如果这三步你都无动于衷,那么 doesNotRecognizeSelector: 才会把你送走。
9.5 总结
Runtime 是 OC 的灵魂。 Swift 很棒,很快,很安全。 但只有在操作 Runtime 的时候,你才会感觉自己像个真正的黑客。