前言
前段时间学习了RxSwift,然而每次想要把某个代理方法暴露给某个类的rx扩展时,都因为记不住语法而苦恼。
于是在强大的好奇心的驱使下,点开了DelegateProxy的源码。结果,阅读下来发现这部分代码堪称经典,运用了iOS和Swift中的多种高级技能,故记录一下思路和收获,并推荐给大家阅读,可以用来进阶或者回顾这些知识点!
具体可以get到什么技能点
- (☆☆☆☆☆) 学会通过OC+Swift混编的方式来利用强大的Runtime
- (☆☆☆☆) 看到如何通过 继承 结合 Protocol 完成很好的封装
- (☆☆☆) 学会使用 associated object(关联对象)来给某个对象添加“变量”
- (☆☆) 加深对 Rx 的理解
- (☆☆) 加深对Delegate的理解
- (☆☆) 其他零碎的知识点
不过,为了阅读这部分代码,需要读者已经对RxSwift比较熟悉,对Swift的语法和iOS的基础知识也有所了解,否则会影响收获。
其他
文中摘录和阅读的RxCocoa版本号为3.5.0(版本号不同代码可能有细节上的差异)
正文
首先,看如何把XXXDelegate的一个方法暴露给XXX对象的rx扩展:
(没错,就是我怎么都记不住的那部分代码,掀桌 (╯‵□′)╯︵┻━┻ ......)
class RxXXXDelegateProxy: DelegateProxy, DelegateProxyType, XXXDelegate {
static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject) {
let xxx:XXX = (object as? XXX)!
xxx.delegate = delegate as? XXXDelegate
}
static func currentDelegateFor(_ object: AnyObject) -> AnyObject? {
let xxx: XXX = (object as? XXX)!
return xxx.delegate
}
}
extension Reactive where Base: XXX {
public var delegate:DelegateProxy {
return RxXXXDelegateProxy.proxyForObject(base)
}
public func setDelegate(_ delegate:XXXDelegate) -> Disposable {
return RxXXXDelegateProxy.installForwardDelegate(
delegate,
retainDelegate: false,
onProxyForObject: self.base)
}
public var aNonVoidMethod: ControlEvent {
let source = delegate.
methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:)))
.map { parameters in
return parameters[1] as! SomeType
}
return ControlEvent(events: source)
}
}
理清对象之间的关系
开始阅读代码运行过程之前,我们先把对象之间的关系弄清楚,会帮助我们理解后面的代码。我们看到:
class RxXXXDelegateProxy: DelegateProxy, DelegateProxyType, XXXDelegate { ... }
所以先来看 协议 DelegateProxyType :
public protocol DelegateProxyType : AnyObject {
public static func createProxyForObject(_ object: AnyObject) -> AnyObject
public static func assignedProxyFor(_ object: AnyObject) -> AnyObject?
public static func assignProxy(_ proxy: AnyObject, toObject object: AnyObject)
public static func currentDelegateFor(_ object: AnyObject) -> AnyObject?
public static func setCurrentDelegate(_ delegate: AnyObject?, toObject object: AnyObject)
public func forwardToDelegate() -> AnyObject?
public func setForwardToDelegate(_ forwardToDelegate: AnyObject?, retainDelegate: Bool)
}
其定义了一系列的方法,然后在extension中实现了两个方法...具体用途,暂不用去管。
然后,这里基本都是定义的协议方法,那么必然有实体类遵循它。我们看到我们前面写的 RxXXXDelegateProxy 遵循了它!咦?但是我们怎么只实现了两个方法,那其他方法是哪里来的呢?考虑到我们的 RxXXXDelegateProxy 还继承了 DelegateProxy,于是进去瞅瞅:
/// Base class for `DelegateProxyType` protocol.
open class DelegateProxy : _RXDelegateProxy { ... }
看来没错,虽然它没有遵循 DelegateProxyType 这个协议,但是上面的一行注释 “Base class for DelegateProxyType protocol” 说明 DelegateProxy 就是实现 DelegateProxyType 中方法的那个类了。
所以,应该是 DelegateProxy 实现了绝大部分的协议方法,然后我们自定义的 RxXXXDelegateProxy 继承自它,继承到了这些已实现的协议方法,然后再添加两个未实现的函数,就可以完全遵循 DelegateProxyType 了。
问题是,怎么又来了一个 _RXDelegateProxy ?再进去看一下,发现这个类竟然是用OC写的,好奇心又一次被激发!!\(≧▽≦)/
首先,发现其继承自 NSObject,说明对象之间的关系到此为止;其次,浏览一下 .m 文件中的代码,发现一堆这样的函数调用(或函数定义):
- protocol_copyMethodDescriptionList
- class_copyProtocolList
- forwardInvocation
它们有一个共同特定,就是来自于Runtime,所以就可以理解为什么要用OC来编写这部分代码了。
(可能有的童鞋要说了,swift 也是支持 Runtime 的呀,但是如果写在 swift 里,动态性就大大减弱了,只能动态获取到继承自 NSObject 的类的信息 和 加了 @dynamic 的变量和函数,这对于一个基础的库是不可取的。)
总结,对象之间的关系如下图:
其中虚线表示虽然没有继承关系,但是基本是按照协议来写的,这有点像『鸭子类型』的编程语言的处理方式。
另外,还有一个问题:
RxXXXDelegateProxy 遵循了 XXXDelegate 协议,
但是奇怪的是并没有实现其定义的方法。
那么是如何在rx里面产生数据流的呢?这个问题稍后再看,记为『遗留问题1』。
开始阅读
接着,我们来阅读代码。
首先,我们在使用时,会直接使用 anObjectOfXXX.rx.aNonVoidMethod,所以,我们定位到起点是 aNonVoidMethod ,那么进入之后,我们看到它相当于是:
RxXXXDelegateProxy
.proxyForObject(base) //Step1
.methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:))) //Step2
.map { parameters in
return parameters[1] as! SomeType
}
所以这里的关键是Step1 和 Step2 这两行,我们潜进去看看:
Step 1
我们发现 proxyForObject 这个函数在 "DelegateProxyType.swift" 这个文件的 DelegateProxyType 这个协议中定义:
// 为了方便阅读,(仅)去掉了一些 Assert语句
extension DelegateProxyType {
public static func proxyForObject(_ object: AnyObject) -> Self {
MainScheduler.ensureExecutingOnScheduler()
let maybeProxy = Self.assignedProxyFor(object) as? Self // A
let proxy: Self
if let existingProxy = maybeProxy {
proxy = existingProxy
}else {
proxy = Self.createProxyForObject(object) as! Self // A2
Self.assignProxy(proxy, toObject: object) // A3
}
let currentDelegate: AnyObject? = Self.currentDelegateFor(object) // B1
if currentDelegate !== proxy {
proxy.setForwardToDelegate(currentDelegate, retainDelegate: false) // B2
Self.setCurrentDelegate(proxy, toObject: object) // B3
}
return proxy
}
解析这段代码之前,先提一点:
由于我们现在知道了对象之间的关系,所以这里Self调用的方法都来自于图1中最左边这一条线路(自下向上)。其中绝大部分在中间的 DelegateProxy 类中,一部分需要自定义的在 RxXXXDelegateProxy 中,另外需要Runtime支持的部分则在 _RXDelegateProxy 中。
上面这段代码可以分为A、B两部分,分别来看:
- A: 先看 assignedProxyFor 和 assignProxy 这两个函数,完全可以看作是 proxy 的getter 和 setter函数。而为了给一个类的.rx扩展添加一个名曰 proxy 的变量,那么只能祭出『关联对象』这一大法了:
// 稍作编辑,省略了中间函数调用
// 用作关联对象的 key 值:
var delegateAssociatedTag: UnsafeRawPointer = UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1))
open class func assignedProxyFor(_ object: AnyObject) -> AnyObject? {
// getter:
let maybeDelegate = objc_getAssociatedObject(object, delegateAssociatedTag)
return castOptionalOrFatalError(maybeDelegate.map { $0 as AnyObject })
}
open class func assignProxy(_ proxy: AnyObject, toObject object: AnyObject) {
precondition(proxy.isKind(of: self.classForCoder()))
// setter:
objc_setAssociatedObject(object, delegateAssociatedTag, proxy, .OBJC_ASSOCIATION_RETAIN)
}
所以,A部分的代码即是,get 这个 RxXXXDelegateProxy 对象 的变量,如果没有,则 create 一个,然后set。
那么我们来看一下create 的过程,createProxyForObject 只是调用了从父类 DelegateProxy 继承来的初始化函数,把Object对象保存在自己的parentObject变量中,便调用了父类的init。
然而我们看到 父类 _RXDelegateProxy 并没有实现 init 函数,而是有一个 initialize 方法,因此实际上 RxXXXDelegateProxy 对象的创建过程应该在这里:
(注意:熟悉 initialize 的童鞋应该知道,实际上 initialize 函数会在最开始执行,也就是在执行 proxyForObject 之前就会调用。不过这个顺序不太影响,为了保持思路的顺畅,放在这里讲)
+(void)initialize {
@synchronized (_RXDelegateProxy.class) {
// 创建一个字典 voidSelectorsPerClass:
if (voidSelectorsPerClass == nil) {
voidSelectorsPerClass = [[NSMutableDictionary alloc] init];
}
// voidSelectors 集合,用来存储 “返回值为空的协议方法”
NSMutableSet *voidSelectors = [NSMutableSet set];
#define CLASS_HIERARCHY_MAX_DEPTH 100
NSInteger classHierarchyDepth = 0;
Class targetClass = NULL;
// 利用 class_getSuperclass 来不断地获取父类 ,但不超过100层(防止堆栈溢出)
// 初始值为self,也就是我们自定义的那个类: RxXXXDelegateProxy
for (classHierarchyDepth = 0, targetClass = self;
classHierarchyDepth < CLASS_HIERARCHY_MAX_DEPTH && targetClass != nil;
++classHierarchyDepth, targetClass = class_getSuperclass(targetClass)
) {
unsigned int count;
// 利用 class_copyProtocolList 获取到该类遵循的各个协议
Protocol *__unsafe_unretained *pProtocols = class_copyProtocolList(targetClass, &count);
// 调用私有方法 collectVoidSelectorsForProtocol 来递归获取各个协议所遵循的协议
for (unsigned int i = 0; i < count; i++) {
// 针对每个协议,将其 “返回值为空的方法”union到voidSelectors中
NSSet *selectorsForProtocol = [self collectVoidSelectorsForProtocol:pProtocols[i]];
[voidSelectors unionSet:selectorsForProtocol];
}
free(pProtocols);
}
if (classHierarchyDepth == CLASS_HIERARCHY_MAX_DEPTH) {
NSLog(@"Detected weird class hierarchy with depth over %d. Starting with this class -> %@", CLASS_HIERARCHY_MAX_DEPTH, self);
}
voidSelectorsPerClass[CLASS_VALUE(self)] = voidSelectors;
}
}
根据上面的代码(及注释),我们可以看到,这里把 RxXXXDelegateProxy 和 自己所有的父类 所遵循的协议(以及协议遵循的协议...)中所有的 『空返回值的协议方法』都保存在 voidSelectorsPerClass 字典中,key 为 object 的地址,value为『空返回值的协议方法』的集合。
(其中,voidSelectorsPerClass 是一个 static 变量,与 initialize 配合,是给OC的类加 class variables 的一个经典做法。)
这里又有一个问题,为什么要找返回值为空的协议方法呢?有什么特殊之处呢?记为『遗留问题2』。
小结:A部分其实是 RxXXXDelegateProxy 对象初始化的过程(若无,则创建自己;如有,则直接返回),并加到对应的XXX对象rx扩展的关联对象中。
- B:这部分主要调用的我们自定义的方法 setCurrentDelegate 和 currentDelegateFor,与A部分类似,这也是一个类似初始化的过程,如果currentDelegate不是自己,则setCurrentDelegate为自己。从此,该XXX对象的delegate就是这个新创建的 RxXXXDelegateProxy 对象了,所有代理方法会发给它。
咦? B2那行的setForwardToDelegate做了什么呢?
可以先记住,作为『遗留问题3』
Step 2
在这一步骤中,RxXXXDelegateProxy 调用了自己的 methodInvoked 方法,并把某个 XXXDelegate 方法的 selector 当做参数传了进去。于是我们去看一下这个方法:
// 省略部分不相关代码
open func methodInvoked(_ selector: Selector) -> Observable<[Any]> {
checkSelectorIsObservable(selector)
if let subject = methodInvokedForSelector[selector] {
return subject.asObservable()
}else {
methodInvokedForSelector[selector] = MessageDispatcher(delegateProxy: self)
return subject.asObservable()
}
}
除了第一句 checkSelectorIsObservable ( 保证该selector方法返回值为空,或 forwardToDelegate 实现了该方法,与『遗留问题2』相关,暂跳过 ),后面的依旧是一个“若有,则返回;若无,则创建”的过程,目标对象是 methodInvokedForSelector[selector].asObservable() ,翻看定义会发现 methodInvokedForSelector 是一个 [Selector: MessageDispatcher] 类型的数组,所以我们需要知道MessageDispatcher的asObservable() 会返回什么。
MessageDispatcher 的主要代码如下:
fileprivate final class MessageDispatcher {
private let dispatcher: PublishSubject<[Any]>
private let result: Observable<[Any]>
init(delegateProxy _delegateProxy: DelegateProxy) {
weak var weakDelegateProxy = _delegateProxy
let dispatcher = PublishSubject<[Any]>()
self.dispatcher = dispatcher
self.result = dispatcher
.do(onSubscribed: { weakDelegateProxy?.reset() }, onDispose: { weakDelegateProxy?.reset() })
.share()
.subscribeOn(mainScheduler)
}
var on: (Event<[Any]>) -> () {
return self.dispatcher.on
}
func asObservable() -> Observable<[Any]> {
return self.result
}
}
所以,其返回的是一个名为 result 的Observable,而这个 result 的数据流来自一个 PublishSubject。
好了,自此就可以解释,为什么 Step2 之后,我们可以 .map 操作数据流,因为之前其创造返回了一个Observable。所以,之后rx.aNonVoidMethod就会产生数据流。但是产生数据流的过程在哪里?仔细一想,应该是a_non_void_method 这个代理方法被调用的时候吧?但是我们自定义的 RxXXXDelegateProxy 没有实现这些代理方法呀?那是如何产生数据流的呢?( 是时候解决『遗留问题1』了!)
Step 1.5
怎么办呢?既然没有实现,却可以得知什么时候调用,想到什么了?再加上前面我们一直提到的OC的Runtime?Bingo!就是『消息转发』。
我们去翻看一下,果然,找到了 forwardInvocation 这个方法:
-(void)forwardInvocation:(NSInvocation *)anInvocation {
// 判断返回值是否为空:
BOOL isVoid = RX_is_method_signature_void(anInvocation.methodSignature);
NSArray *arguments = nil;
// 若为空,则 _sentMessage
if (isVoid) {
arguments = RX_extract_arguments(anInvocation);
[self _sentMessage:anInvocation.selector withArguments:arguments];
}
// 如果 _forwardToDelegate 实现了该方法,则调用
if (self._forwardToDelegate && [self._forwardToDelegate respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:self._forwardToDelegate];
}
// 若为空,则 _methodInvoked
if (isVoid) {
[self _methodInvoked:anInvocation.selector withArguments:arguments];
}
}
从注释中我们可以看到,这里做了这么一件事:
如果 _forwardToDelegate 实现了该方法,则去调用。如果该方法返回值为空,则在调用前后分别执行 _sentMessage 和 _methodInvoked。
返回值为空这件事情,我们继续跳过 (『遗留问题2』),_forwardToDelegate 这个呢?也继续跳过(『遗留问题3』)。
先去看这两个方法,结果发现是空的方法,注释为“abstract method”,那就去看子类喽:
open override func _sentMessage(_ selector: Selector, withArguments arguments: [Any]) {
sentMessageForSelector[selector]?.on(.next(arguments))
}
open override func _methodInvoked(_ selector: Selector, withArguments arguments: [Any]) {
methodInvokedForSelector[selector]?.on(.next(arguments))
}
Gotcha!原来是这里产生的数据流,根据前面 MessageDispatcher 的代码,最终是 MessageDispatcher 中的dispatcher(PublishSubject)产生了数据流,数据为 arguments 数组。
上面我们注意到了另一个方法:_sentMessage,那么应该也有对应的 喽,往上翻看代码,就会找到。所以,如果我们想获取代理方法执行后产生的数据流,则:
RxXXXDelegateProxy.delegate
.methodInvoked(#selector(XXXDelegate.a_non_void_method(_:param:)))
...
想获取代理方法执行之前产生的数据流,则:
RxXXXDelegateProxy.delegate
.sentMessage(#selector(XXXDelegate.a_non_void_method(_:param:))) //Step2
...
然而,就在我们翻看代码寻找 sentMessage 函数的时候,必然会看到一堆注释:
Only methods that have
void
return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not. Observing is performed by installing a hidden associatedPublishSubject
that is used to dispatch messages to observers.
Delegate methods that have non
void
return value can't be observed directly using this method. Because:
* those methods are not intended to be used as a notification mechanism, but as a behavior customization mechanism
* there is no sensible automatic way to determine a default return value
In case observing of delegate methods that have return type is required, it can be done by manually installing a
PublishSubject
orBehaviorSubject
and implementing delegate method.
这三段注释写的非常好,第一段基本上总结了 整个DelegateProxy的作用和原理;第二段解释了我们的『遗留问题2』,即为什么要费尽心思值过滤掉返回值非空的代理方法,因为 返回值非空的代理方法 不是用来做通知策略的,而是用做 定义对象行为 的(比如UITableView的 numberOfSections 等);第三段则告诉我们,如果真正有需求观测 返回值非空的代理方法,则可以自己 install 一个 PublishSubject。这个也跟我们的 『遗留问题3』有关。
我们的 『遗留问题3』 是:
在给XXX对象设置代理的时候,除了setCurrentDelegate,
还调用了setForwardToDelegate,这是做什么的呢?
后来在消息转发的时候,如果 _forwardToDelegate 这个变量实现了某个selector,
会去调用它实现的方法,这个在做什么?
简单浏览代码,我们发现setForwardToDelegate果然就是设置了_forwardToDelegate而已,最开始默认设置为自己(RxXXXDelegateProxy)。
同时,我们回顾最开始,我们为了实现某些代理方法,需要实现那些非空的代理方法,此时就需要:
public func setDelegate(_ delegate:XXXDelegate) -> Disposable {
return RxXXXDelegateProxy.installForwardDelegate(
delegate,
retainDelegate: false,
onProxyForObject: self.base)
}
到 installForwardDelegate 里一看,同样调用了setForwardToDelegate,只不过,这次,它设置的参数中的delegate为_forwardToDelegate。
所以 _forwardToDelegate 是为了 接收「向前转发的代理方法」的一个对象,其实也就是我们之前正常使用的那个代理。此时再去看 forwardInvocation,感觉特别像AOP(面向切片编程,让我想起了给python函数加装饰器的感觉),即:
最开始的代理为A,结果我们把代理改为AProxy,然后把A设置为AProxy的 _forwardToDelegate。于是,所有的代理方法将会到达AProxy,AProxy针对这些方法进行一下操作:
+ 提示方法将要被调用
调用_forwardToDelegate的原代理方法
+ 提示方法已经被调用
总结
就酱,原来看似很复杂的 DelegateProxy 只是一个通过『关联对象』给扩展加上的一个『用 Runtime 来转发代理方法 & 在返回值为空的代理方法调用前后分别产生两种数据流』的代理。
Where to go from here?
- 虽然自认为写的已经很详细,主干已经理清,但是仍然还有一些细枝末节可以去探究
- 针对不熟悉或遗忘的地方可以再去阅读其他资料进行学习、实践练习
- 我们通过阅读这部分代码,知道了它是什么样子的,甚至知道了它为什么是这个样子的,但是,还可以去思考,它为什么不能是其他样子呢?是会影响性能吗?还是会造成封装不好?或者,也许你通过思考最终发现,它还可以是其他更好的样子,那么提前恭喜你!