RxSwift之sentMessage、methodInvoked失效问题解决

RxSwift的sentMessagemethodInvoked方法


RxSwiftRxSwift版本,用来实现函数式、响应式编程。

具体RxSwift的很多用法不做介绍,接下来,只讨论sentMessagemethodInvoked这两个方法,其作用是返回一个Observable<[Any],可以作为观察者监控NSObject子类的某个selector,当执行该selector时,将在执行前、后分别执行注册了该selectorsentMessagemethodInvoked方法。

sentMessagemethodInvoked实现原理


sentMessagemethodInvoked只针对某个实例起作用,其实现首先借鉴了KVO的实现方法,通过创建监听的对象的子类,然后重写方法的实现来实现。sentMessagemethodInvoked实现分两个版本,基础版、优化版。基础版通过Swizzle forwardInvocation:respondsToSelector:methodSignatureForSelector:等函数,将所有需要观察的selector调用时进入forwardInvocation:流程,从而进行拦截,以实现通知;优化版则在基础版之上通过Type Encoding来做一个缓存优化,避免每次调用都进入转发的过程。

sentMessagemethodInvoked遇到的问题


在使用sentMessagemethodInvoked方法时,发现一个问题,当观察iOS Framework提供的方法时,可以正常运行,但是当观察自己创建的类(NSObject的子类)的实例的方法时,却始终无法运行,查看整个运行机制,ClassIMP都被正确替换,但是调用方法时,却始终截取不到,这下才意识到,难道是被编译器优化掉了?导致没有走Objective-C的动态派发?
遂翻看AppleUsing Swift with Cocoa and Objective-C,有一段话是这么说的:

“When Swift APIs are imported by the Objective-C runtime, there are no guarantees of dynamic dispatch for properties, methods, subscripts, or initializers. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime.

You can use the dynamic modifier to require that access to members be dynamically dispatched through the Objective-C runtime. Requiring dynamic dispatch is rarely necessary. However, it is necessary when using APIs like key–value observing or the method_exchangeImplementations function in the Objective-C runtime, which dynamically replace the implementation of a method at runtime. If the Swift compiler inlined the implementation of the method or devirtualized access to it, the new implementation would not be used.”

大概意思是当在Swift中使用Objective-C中定义的方法时,编译器不保证动态派发,会对其做优化来提高性能,导致的结果是,会使KVOSwizzling等失效,要解决该问题,可以使用dynamic修饰符,使得方法强制进行Objective-C的动态派发。

这样,问题就解决了,原来是编译器优化搞的鬼。
stackoverflow上也有人提这个问题,我也进行了回答。