Objective-C 通信模式(Communication Patterns)

在每一个应用程序中都是包含着许多松散耦合的对象,它们之间采用何种通信模式进行交互?哪一种通信机制是最佳的实践?这些都是值得思考的问题(诚然其中没有明确的答案,但是也有一些约定俗成的用法习惯)。

通常情况下,我们可以采用的通信模式包括:KVO,Notifications,delegation,blocks,target-action。

一、了解Communication Patterns

1、KVO

KVO机制用于通知对象属性的变化。

消息接收者-->接收对象属性变化的消息;消息发送者-->对象属性发生变化。

消息接收者通过注册对消息发送者的监听,还必须知道消息发送者的生命期,用以解除对其的注册。


2、Notifications

Notifications(消息通知)用于在代码间广播消息。

Notifications发送的消息是任意的,你可以通过 userInfo 字典或者用 NSNotification 消息载体装载发送的消息

通过Notifications,消息发送者和接收者可以互不认识,即在松耦合的代码间传递消息。此外,这是一种单向的通信方式,即接收方不能回复Notifications的消息通知。


3、Delegation(委托)

委托的作用:传值;传事件。

在委托协议中定义任意需要的委托方法处理两个特定对象之间的通信。发送者即委托者,在委托协议中定义委托方法,接收者即被委托者,实现委托协议中的委托方法。


4、Blocks

Blocks通常用于回调事件处理等等,是一种相对较新的技术。一般情况下,block可以满足用delegation实现的消息传递机制,不过这两种机制还是有各自的特色。

潜在的风险:retain cycle。由于被blocks引用的变量都会被自动 retain 一次,如果不能保证被引用的变量置nil,那么使用Blocks就会出现 retain cycles。

例如:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    [manager release];
};
在这个例子中,由于manager和block相互持有,那么即使调用了release,还是形成了retain cycle。


为了解除retain cycle,那么代码修改为:

DoSomethingManager *manager = [[DoSomethingManager alloc] init];
manager.complete = ^{
    //...complete actions
    [manager otherAction];
    manager.complete = nil;
    [manager release];
};

由于 manager.complete = nil 这样就破坏了retain cycle。


再看一个NSOperation的例子:

self.queue = [[NSOperationQueue alloc] init];
MyOperation *operation = [[MyOperation alloc] init];
operation.completionBlock = ^{
    [self finishedOperation];
};
[self.queue addOperation:operation];

乍一看,似乎存在一个retain cycle: self --retain--> queue --retain--> operation --retain--> blcok --retain-->self

但是,一个operation添加到queue后,必定会在某一个时刻执行结束,然后退出queue,那么一旦operation退出queue后,retain cycle也就被打破了。


5、Target-Action

Target-Action是用来响应UI界面事件发送消息的典型模式。

及时消息响应的target为nil空,action也会沿着 responder chain(响应链)找到一个对象进行响应。

Target-Action的限制是消息发送不能携带任何自定义的负载消息。


二、Making the Right Choice

基于上述通信模式的特点,构建了如下的流程图,有助于在选择哪种模式做出正确的决定。(这张图的建议并不一定是最终的答案)

Objective-C 通信模式(Communication Patterns)_第1张图片



三、Framework Examples

1、KVO

NSOperationQueue 使用 KVO 机制观察 operation 的状态属性变化(isFinished,isExecuting,isCancelled),当operation的状态属性变化时,NSOperationQueue 就会接收到一个 KVO 消息通知。

NSOperationQueue -- 消息的接收者

operation -- 消息的发送者

显然,二者需要一个单向的通信机制,而且NSOperationQueue 只对 operation对象的状态属性的变化感兴趣,所以采用KVO是最佳的实践。

Objective-C 通信模式(Communication Patterns)_第2张图片

诚然,KVO是非常不错的实践方式。当然似乎也可以采用Delegation实现。

operation queue 是 operation 的委托(delegation),operation 可以调用诸如 operationDidFinish 或者 operationDidBeginExecuting 等委托协议方法通知 operation queue 有关 operation 状态属性的变化。


2、Notifications

Core Data 使用 notification 在事件间通信,例如:managed object context(NSManagedObjectContextDidChangeNotification)

managed object context 发送的变化通知,其消息接收方可以不必知晓谁是发送者,而且这不是一个UI事件,那么其接收者可以多个。说明这里需要一个单向的通信管道,那么 Notifications 就是唯一的选择了。

Objective-C 通信模式(Communication Patterns)_第3张图片


3、Delegation

Table View 的实现就采用了 Delegation 。例如tableView:didSelectRowAtIndexPath: 方法。为什么要以delegate调用的方式来实现?而又为啥不用target-action方式?


正如我们在流程图中看到的一样,使用target-action时,不能传递自定义的数据。而在选中table view的某个cell时,collection view不仅仅需要告诉我们有一个cell被选中了,还需要告诉我们是哪个cell被选中了(index path)。按照这样的一种思路,那么从流程图中可以看到应该使用delegation机制。

Objective-C 通信模式(Communication Patterns)_第4张图片


4、Blocks

关于block的介绍,我们来看看[NSURLSession dataTaskWithURL:completionHandler:]吧。从URL loading system返回到调用者,这个过程具体是如何传递消息的呢?首先,作为这个API的调用者,我们知道消息的发送者,但是我们并没有retain这个发送者。另外,这属于单向消息传递——直接调用dataTaskWithURL:方法。如果按照这样的思路对照着流程图,我们会发现应该使用基于block消息传递的机制。

Objective-C 通信模式(Communication Patterns)_第5张图片

还有其它可选的机制吗?当然有了,苹果自己的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,不过是用于别的目的)。


5、Target-Action

Target-Action用的最明显的一个地方就是button(按钮)。button除了需要发送一个click事件以外,并不需要再发送别的信息了。所以Target-Action在用户界面事件传递过程中,是最佳的选择。

Objective-C 通信模式(Communication Patterns)_第6张图片

如果taget已经明确指定了,那么action消息回直接发送给指定的对象。如果taget是nil,action消息会以冒泡的方式在响应链中查找一个能够处理该消息的对象。此时,我们拥有一种完全解耦的消息传递机制——发送者不需要知道接收者,以及其它一些信息。


Target-Action非常适用于用户界面中的事件。目前也没有其它合适的消息传递机制能够提供同样的功能。虽然notification最接近这种在发送者和接收者解耦关系,但是target-action可以用于响应链(responder chain)——只有一个对象获得action并作出响应,并且action可以在响应链中传递,直到遇到能够响应该action的对象。


四、小结

对于两个对象之间的消息传递使用何种通信模式有时的确有些模棱两可,但是仔细考虑琢磨,会发现其各自有的需求和功能特性。

文章中给出的决策流程图只是选择时候提供的参考依据,并非最终的答案,实践出真知!



注:本篇文章源自 objc.io  文章 Communication Patterns ,其全文翻译:iOS中消息的传递机制

你可能感兴趣的:(Objective-C,消息传递机制)