首页 > 代码库 > IOS OS X 中集中消息的传递机制

IOS OS X 中集中消息的传递机制

1 KVO (key-value Observing)

  是提供对象属性被改变是的通知机制。KVO的实现实在Foundation中,很多基于 Foundation 的框架都依赖与它。如果只对某一个对象的值的改变感兴趣的话。就可以使用KVO消息传递。满足KVO的前提条件:1接受者(接受对象改变的通知的对象)需要知道发送者(值会改变的对象);2,接受者需要知道发送者的生命周期,因为它需要在发送者被销毁前注销观察者身份。如果这两个要求都符合的话,这个消息传递机制可以一对多(多个观察者可以注册同一个对象的变化)

如果要在Core Data 上使用KVO的话,方法会有些许差别。这和Core Data的惰性加载(faulting) 机制有关,一旦一个managed object 被惰性加载处理的话,即使它的属性没有被改变,它还是会出发相应的观察者。

注:把属性值先取入缓存中,在对象需要的时候再进行一次访问,这在 Core Data 中是默认行为,这种技术称为 Faulting。这么做可以避免降低内存开销,但是如果你确定将访问结果对象的具体属性值时,可以禁用 Faults 以提高获取性能。

2 通知 (Notification)

  要在代码中的两个不相关的模块中传递消息时,通知机制是非常好的工具。通知机制广播消息,当消息内容丰富而且无需指望接受者一定要关注的话这一招特别有用

通知可以用来发送任意消息,甚至可以包含一个userInfo字典。你可以继承NSNotification 写一个自己的通知类类自定义行为,通知的独特之处在于,发送者和接受者不需要相互知道对方,所以通知可以被用来在不同的相隔很远的模块之间传递消息。这就意味着这种消息的传递是单向的,我们不能回复通知/

3委托(Delegation)

  Delegation 在平果的框架中广泛存在,它让我们能自定义对象的行为,并收到一些触发的事件,要使用Delegation模式的话,发送者需要知道接受者,但是反过来没有要求,应为发送者只需要知道接受者符合一定的协议,所以他们两者结合的很松。

应为delegation 协议可以定义任何的方法,我们可以照着自己的需求来传递消息。可以用方法参数来传递消息的内容,delegation 可以通过方悔之的形式来给发送者做出回应。如果只要在相对接近的两个模块间传递消息 delegation 是很灵活很直接的消息传递机制。

过度使用 delgation 也会带来风险。如果恋歌对相结合的很紧密,任何其中一个对象都不能单独运转,那么就不需要用delegate协议了 这些情况下,对象已经知道各自的类型 可以直接交流

块 (Block)

Block 是最近才加入 Objective-C 的,首次出现在 OS X 10.6 和 iOS 4 平台上。Block 通常可以完全替代 delegation 消息传递机制的角色。不过这两种机制都有它们自己的独特需求和优势。

一个不使用 block 的理由通常是 block 会存在导致 retain 环 (retain cycles) 的风险。如果发送者需要 retain block 但又不能确保引用在什么时候被赋值为 nil, 那么所有在 block 内对 self 的引用就会发生潜在的 retain 环。

假设我们要实现一个用 block 回调而不是 delegate 机制的 table view 里的选择方法,如下所示:

self.myTableView.selectionHandler = ^void(NSIndexPath *selectedIndexPath) { // 处理选择 };

这儿的问题是,self 会 retain table view,table view 为了让 block 之后可以使用而又需要 retain 这个 block。然而 table view 不能把这个引用设为 nil,因为它不知道什么时候不需要这个 block 了。如果我们不能保证打破 retain 环并且我们需要 retain 发送者,那么 block 就不是一个的好选择。

NSOperation 是使用 block 的一个好范例。因为它在一定的地方打破了 retain 环,解决了上述的问题。

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

一眼看来好像上面的代码有一个 retain 环:self retain 了 queue,queue retain 了 operation, operation retain 了 completionBlock, 而 completionBlock retain 了 self。然而,把 operation 加入 queue 中会使 operation 在某个时间被执行,然后被从 queue 中移除。(如果没被执行,问题就大了。)一旦 queue 把 operation 移除,retain 环就被打破了。

另一个例子是:我们在写一个视频编码器的类,在类里面我们会调用一个 encodeWithCompletionHandler: 的方法。为了不出问题,我们需要保证编码器对象在某个时间点会释放对 block 的引用。其代码如下所示:

@interface Encoder ()@property (nonatomic, copy) void (^completionHandler)();@end@implementation Encoder- (void)encodeWithCompletionHandler:(void (^)())handler{    self.completionHandler = handler;    // 进行异步处理...}// 这个方法会在完成后被调用一次- (void)finishedEncoding{    self.completionHandler();    self.completionHandler = nil; // <- 不要忘了这个!}@end

一旦任务完成,completion block 调用过了以后,我们就应该把它设为 nil

如果一个被调用的方法需要发送一个一次性的消息作为回复,那么使用 block 是很好的选择, 因为这样做我们可以打破潜在的 retain 环。另外,如果将处理的消息和对消息的调用放在一起可以增强可读性的话,我们也很难拒绝使用 block 来进行处理。在用例之中,使用 block 来做完成的回调,错误的回调,或者类似的事情,是很常见的情况。

Target-Action

Target-Action 是回应 UI 事件时典型的消息传递方式。iOS 上的 UIControl 和 Mac 上的 NSControl/NSCell 都支持这个机制。Target-Action 在消息的发送者和接收者之间建立了一个松散的关系。消息的接收者不知道发送者,甚至消息的发送者也不知道消息的接收者会是什么。如果 target 是 nil,action 会在响应链 (responder chain) 中被传递下去,直到找到一个响应它的对象。在 iOS 中,每个控件甚至可以和多个 target-action 关联。

基于 target-action 传递机制的一个局限是,发送的消息不能携带自定义的信息。在 Mac 平台上 action 方法的第一个参数永远接收者。iOS 中,可以选择性的把发送者和触发 action 的事件作为参数。除此之外就没有别的控制 action 消息内容的方法了。

做出正确的选择

基于上述对不同消息传递机制的特点,我们画了一个流程图来帮助我们在不同情境下做出不同的选择。一句忠告:流程图的建议不代表最终答案。有些时候别的选择依然能达到应有的效果。只不过大多数情况下这张图能引导你做出正确的决定。

communication-patterns-flow-chart

 

图中有些细节值得深究:

有个框中说到: 发送者支持 KVO。这不仅仅是说发送者会在值改变的时候发送 KVO 通知,而且说明观察者需要知道发送者的生命周期。如果发送者被存在一个 weak 属性中,那么发送者有可能会自己变成 nil,那时观察者会导致内存泄露。

一个在最后一行的框里说,消息直接响应方法调用。也就是说方法调用的接收者需要给调用者一个消息作为方法调用的直接反馈。这也就是说处理消息的代码和调用方法的代码必须在同一个地方。

最后在右下角的地方,一个选择分支这样说:发送者能确保释放对 block 的引用吗?这涉及到了我们之前讨论 block 的 API 存在潜在的 retain 环的问题。如果发送者不能保证在某个时间点会释放对 block 的引用,那么你会惹上 retain 环的麻烦。