几种消息传递机制的比较

本文主要借鉴了 objc上的文章

消息传递机制

iOS中,消息传递机制主要有 5种

  • KVO
  • Notification
  • Delegate
  • Target-Action
  • Block

本文会比较他们之间的区别和使用场景。

1. 介绍:

首先,我们了解一个概念: 消息的发送者和接受者。

  • 对于delegate,tableView是发送者,它的delegate是接受者。
  • 对于Notification,postNotification的是发送者, addObserve的是接受者。
  • 对于target-action, button是消息的发送者,实现action的是消息的接受者。
  • 对于KVO,修改一个支持 KVO 的对象的对象是发送者,这个 KVO 对象的观察者就是接收者。
  • 对于block,执行该block的是发送者block() , 定义该block的block=^{ ... }的是接受者。
1.1 KVO

KVO 是提供对象属性被改变时的通知的机制,如果只对某个对象的值的改变感兴趣的话,就可以使用 KVO 消息传递。
有两个前提:

  • 接收者(接收对象改变的通知的对象)需要知道发送者 (值会改变的对象)。 即接受者需要知道观察的是谁的什么属性。
  • 接收者需要知道发送者的生命周期,因为它需要在发送者被销毁前注销观察者身份。
1.2 通知Notification

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

  • 发送者和接收者不需要相互知道对方
  • 消息传递是单向的,我们不能回复一个通知
1.3 委托代理Delegate

delegate让我们能自定义对象的行为,并发出一些触发的事件的消息。在相对接近的两个模块间传递消息。

  • 发送者需要知道接收者,但是反过来没有要求。因为发送者只需要知道接收者符合一定的协议,所以它们两者结合的很松。
  • 发送者可以用方法参数来传递消息内容,delegate 可以通过返回值的形式来给发送者作出回应。
1.4 Block

Block 通常可以完全替代 delegation 消息传递机制的角色。

//问题:retain环
block会存在导致retain环的风险。如果发送者需要 retain block 但又不能确保引用在什么时候被赋值为 nil, 那么所有在 block 内对 self 的引用就会发生潜在的 retain 环。

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

// 此处,self引用tableView,tableView引用block。 tableView因为不知道什么时候不需要这个block了,所以不能把引用设置为nil。 如果block引用了self,那么会形成引用环。

Operation使用block的处理方式

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

// self retain了queue,queue retain了Operation, Operation 又retain了completionBlock,而completionBlock 又retain了self。 把 operation 加入 queue 中会使 operation 在某个时间被执行,然后被从 queue 中移除。(如果没被执行,问题就大了。)一旦 queue 把 operation 移除,retain 环就被打破了。

另外一个例子

@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 blcok被调用后,我们把它设定为nil。

如上边的例子,retain环的解决方式就是打破引用环。

block和delegate相比:

    1. 一个被调用的方法需要发送一个一次性的消息作为回复,那么使用 block 是很好的选择,直接使用nil打破retain环。
    1. 使用 weak和strong 关键字来打破引用环(我们通常的做法)。
    1. 如果将处理的消息和对消息的调用放在一起可以增强可读性的话,我们也很难拒绝使用 block 来进行处理。
1.5 target-action

Target-Action 是回应 UI 事件时典型的消息传递方式。

  • 消息的接收者不知道发送者。
  • 如果接受者是 nil,action 会在响应链中被传递下去,直到找到一个响应它的对象
  • 发送的消息不能携带自定义的信息

2. 正确的选择

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

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

  • 在右下角的地方,一个选择分支这样说:发送者能确保释放对 block 的引用吗? 就是retain环那个。

3. framework示例
3.1 KVO

NSOperationQueue 用了 KVO 观察队列中的 operation 状态属性的改变情况 (isFinished,isExecuting,isCancelled)。当状态改变的时候,队列会收到 KVO 通知.

消息的接收者(operation 队列)知道消息的发送者(operation),并 retain 它并控制后者的生命周期。另外,在这种情况下只需要单向的消息传递机制。


kvo-flow-chart.png
3.2 Notification

Core Data 使用 notification 传递事件.

接收者不知道消息的发送者。因为消息的源头不是一个 UI 事件,很多接收者可能在关注着此消息,并且消息传递是单向的,所以 notification 是唯一可行的选择。


notification-flow-chart.png
3.3 Delegate

Table view 的 delegate 有多重功能。

用 target-action 时,不能传递自定义的数据。而选中 table view 的某个 cell 时,collection view 不仅需要告诉我们一个 cell 被选中了,也要通过 index path 告诉我们哪个 cell 被选中了。如果我们照着这个思路,流程图会引导我们使用 delegation 机制。
delegation-flow-chart.png
3.4 block

我们用 -[NSURLSession dataTaskWithURL:completionHandler:] 来作为一个 block API 的介绍。

作为 API 的调用者,我们知道消息的发送者,但是我们并没有 retain 它。另外,这是个单向的消息传递————它直接调用 dataTaskWithURL: 的方法。如果我们对照流程图,会发现这属于 block 消息传递机制。
block-flow-chart.png
3.5 target-action

一个明显的 target-action 用例是按钮。按钮在不被按下的时候不需要发送任何的信息。为了这个目的,target-action 是 UI 中消息传递的最佳选择。


target-action-flow-chart.png

如果 target 是明确指定的,那么 action 消息会发送给指定的对象。如果 target 是 nil, action 消息会一直在响应链中被传递下去,直到找到一个能处理它的对象。在这种情况下,我们有一个完全解耦的消息传递机制:发送者不需要知道接收者,反之亦然。Target-action 机制非常适合响应 UI 的事件

4. 总结

我个人的总结:

  • 发送者不知道接受者,接受者不知道发送者: target-action(没有自定义消息) Notification(自定义消息)。 (target-action有响应者链)可以有多个接受者,单向消息传递。

  • 接受者知道发送者,:KVO(主要是属性值的改变消息),可以有多个接受者,单向消息传递。

  • 发送者知道接受者: block(可能有retain环,消息的发送和接受在一个地方),delegate, 双向消息传递,灵活的自定义消息。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,640评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,254评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,011评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,755评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,774评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,610评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,352评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,257评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,717评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,894评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,021评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,735评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,354评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,936评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,054评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,224评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,974评论 2 355

推荐阅读更多精彩内容

  • 每个应用或多或少都由一些需要相互传递消息的对象结合起来以完成任务。在这篇文章里,我们将介绍所有可用的消息传递机制,...
    爱敲代码的果果阅读 3,969评论 0 2
  • 每个应用程序或多或少,都由一些松耦合的对象构成,这些对象彼此之间要想很好的完成任务,就需要进行消息传递。 本文将介...
    石丘阅读 1,723评论 4 7
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,392评论 8 265
  • 外出吃晚饭,遇见同事吃宵夜,见识了她健谈却非常有原则的儿子:问他喝不喝饮料,他说,我饮料就不喝了,对身体不好,我只...
    老瑜头阅读 296评论 0 0
  • 这样温柔的雾霭,几时能有啊。 晨光熹微,车笛轻鸣,打开窗户就能闻到隔夜发了酵的泥土之芬芳。飞鸟在高处扑棱着翅膀,天...
    浮辞z阅读 124评论 0 0