虽然这一期的主题是关于Foundation Framework的,不过本文中还介绍了一些超出Foundation Framework(KVO和Notification)范围的一些消息传递机制,另外还介绍了delegation,block和target- action。
本文中,会经常提及接收者[recipient]和发送者[sender]。在消息传递机制中具体是什么意思,我们可以通过一个示例来解释:一 个table view是发送者,而它的delegate就是接收者。Core Data managed object context是notification的发送者,而获取这些notification的主体则是接收者。一个滑块(slider)是action消息 的发送者,而在代码里面对应着实现这个action的responder就是接收者。对象中的某个属性支持KVO,那么谁修改这个值,谁就是发送者,对应 的观察者(observer)则是接收者。
首先我们来看看每种机制的具体特点。在下一节中,我会结合一个流程图来介绍如何在具体情况下,选择正确的消息传递机制。最后,将介绍一些来自苹果Framework中的示例,并会解释在某种确定情况下为什么要选择固定的机制。
- 根据上面讨论的结果,这里我画了一个流程图,来帮助我们何时使用什么消息传递机制做出更好的决定。忠告:流程图中的建议并非最终的答案;可能还有别的选项依然能实现目的。只不过大多数情况下此图可以引导你做出正确的决定。
- 上图中,还有一些细节需要做更近一步的解释:
- 上图中的有个盒子这样说到:sender is KVO compliant(发送者支持compliant)。这不仅以意味着当值发生改变时,发送者会发送KVO通知,并且观察者还需要知道发送者的生命周期。 如果发送者被存储在一个weak属性中,那么发送者有可能被nil掉,进而引起观察者发生leak。
- 另外底部的一个盒子说到:message is direct response to method call(消息直接在方法的调用代码中响应)。也就是说处理消息的代码跟方法的调用代码处于相同的地方。
- 最后,在左下角,处于一个决策问题的判断状态:sender can guarantee to nil out reference to block?(发送者能够确保nil掉到block的引用吗?),这实际上涉及到之前我们讨论到基于block 的APIs已经潜在的retain环。使用block时,如果发送者不能保证在某个实际能够把对block的引用nil掉,那么将会遇到retain环的 问题。
本节我们通过一些来自苹果Framework的示例,来看看在实际使用某种机制之前,苹果是处于何种原因做出选择的。
KVO
NSOperationQueue就是lion给了KVO来观察队列中operation状态属性的改变情况(isFinished, isExecuting, isCancelled)。当状态发生了改变,队列会受到一个KVO通知。为什么operationqueue要是用KVO呢?
消息的接收者(operation queue)明确的知道发送者(opertation),以及通过retain来控制operation的生命周期。另外,在这种情况下,只需要单向的消 息传递机制。当然,如果这样考虑:如果operation queue只关心operation值的改变情况,可能还不足以说服大家使用KVO。但是我们至少可以这样理解:什么机制可以对值的改变进行消息传递呢。
当然KVO也不是唯一的选择。我们可以这样设计:operation queue作为operation的delegate,operation会调用类似operationDidFinish: 或 operationDidBeginExecuting: 这样的方法,来将它的state传递给queue。这样一来,就不太方便了,因为operation需要将其state属性保存下来,一遍调用这些 delegate方法。另外,由于queue不能主动获取state信息,所以queue也必须保存着所有operation的state。
Notifications
Core Data使用notification来传递事件(例如一个managed object context内部的改变——NSManagedObjectContextDidChangeNotification)。
change notification是由managed object context发出的,所以我们不能确定消息的接收者一定知道发送者。如果消息并不是一个UI事件,而有可能多个接收者对该消息感兴趣,并且消息的传递属 于单向(one-way communication channel),那么notification是最佳选择。
Delegation
Table view的delegate有多种功能,从accessory view的管理,到屏幕中cell显示的跟踪,都与delegate的功劳。例如,我们来看看 tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?
正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。
如果消息传递中,不包含选中cell的index path,而是每当选中项改变时,我们主动去table view中获取到选中cell的相关信息,会怎样呢?其实这会非常的麻烦,因为这样一来,我们就必须记住当前选中项相关数据,以便获知被选中的cell。
同理,虽然我们也可以通过观察table view中选中项的index paths属性值,当该值发生改变时,获得一个选中项改变的通知。不过,我们会遇到与上面同样的问题:不做任何记录的话,我们如何获知被选中项的相关信息。
Blocks
关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送 者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消 息传递的机制。
还有其它可选的机制吗?当然有了,苹果自己的NSURLConnection就是最好的例子。NSURLConnection在block问世 之前就已经存在了,所以它并没有利用block进行消息传递,而是使用delegation机制。当block出现之后,苹果在 NSURLConnection中添加了sendAsynchronousRequest:queue:completionHandler:方法 (OSX 10.7 iOS 5),因此如果是简单的task,就不必在使用delegate了。
在OS X 10.9 和 iOS 7中,苹果引入了一个非常modern的API:NSURLSession,其中使用block当做消息传递机制(NSURLSession仍然有一个delegate,不过是用于别的目的)。
Target-Action
Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。
如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。
Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification 最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。
小结
- 首次接触这些机制,感觉它们都能用于两个对象间的消息传递。但是仔细琢磨一番,会发现它们各自有其需求和功能。