RxSwift之sentMessage、methodInvoked失效问题解决
RxSwift的sentMessage
、methodInvoked
方法
RxSwift是Rx的Swift
版本,用来实现函数式、响应式编程。
具体RxSwift
的很多用法不做介绍,接下来,只讨论sentMessage
、methodInvoked
这两个方法,其作用是返回一个Observable<[Any]
,可以作为观察者监控NSObject
子类的某个selector
,当执行该selector
时,将在执行前、后分别执行注册了该selector
的sentMessage
、methodInvoked
方法。
sentMessage
、methodInvoked
实现原理
sentMessage
、methodInvoked
只针对某个实例起作用,其实现首先借鉴了KVO
的实现方法,通过创建监听的对象的子类,然后重写方法的实现来实现。sentMessage
、methodInvoked
实现分两个版本,基础版、优化版。基础版通过Swizzle
forwardInvocation:
、respondsToSelector:
、methodSignatureForSelector:
等函数,将所有需要观察的selector
调用时进入forwardInvocation:
流程,从而进行拦截,以实现通知;优化版则在基础版之上通过Type Encoding
来做一个缓存优化,避免每次调用都进入转发的过程。
sentMessage
、methodInvoked
遇到的问题
在使用sentMessage
、methodInvoked
方法时,发现一个问题,当观察iOS
Framework
提供的方法时,可以正常运行,但是当观察自己创建的类(NSObject
的子类)的实例的方法时,却始终无法运行,查看整个运行机制,Class
、IMP
都被正确替换,但是调用方法时,却始终截取不到,这下才意识到,难道是被编译器优化掉了?导致没有走Objective-C
的动态派发?
遂翻看Apple
的Using 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
中定义的方法时,编译器不保证动态派发,会对其做优化来提高性能,导致的结果是,会使KVO
、Swizzling
等失效,要解决该问题,可以使用dynamic
修饰符,使得方法强制进行Objective-C
的动态派发。
这样,问题就解决了,原来是编译器优化搞的鬼。
stackoverflow上也有人提这个问题,我也进行了回答。