iOS开发基础:消息传递机制的不同与缺陷

图片发自简书App

iOS中的消息传递机制有以下几种:

  1. 代理(Delegation)
  2. 通知(NSNotification)
  3. BLOCK
  4. KVO(key-value observing)
  5. Target-Action
    这么多的消息传递机制,我们该如何选择呢?

最最基本的消息传递机制

其实,除了以上列举的5种以外,还有一类,往往被我们忽视,那就是

方法调用本身就是一种消息传递机制

方法调用可以代入参数,执行完成后,会有返回值,告诉调用者方法执行的结果。
由于返回值只能有一个,并且只在方法执行完成后才“返回”,因此对于需要反映执行过程的任务,比如下载进度,方法调用的局限就十分明显了,另外,从线程角度来看,方法调用是在调用者当前线程开辟的,属于一种同步方法,调用者会被“卡死”,直到方法执行完成。

代理(Delegation)

代理机制就是为了解决任务执行中,需要与调用者“交互”而出现的,UITableView的“三大问题”就是典型的“交互式”调用:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

每次 reloadData ,以上方法都会被“自动”调用一次或多次,因此给了调用者一个机会,可以改变行数或是Cell的样式,给用户一种“动态变化”的效果。
代理机制有以下缺点:

  1. 定义繁琐
    代理机制使用的是OC语言的协议, 首先需要定义协议:
@protocol MyDelegate
- (int)funWithArg:(int)arg1;
@end

@interface My : NSObject { 
    id<MyDelegate> deleage;
}
@property(assign,nonatomic) id<MyDelegate> delegate;
@end

调用者需要实现该协议:

@interface Caller : NSObject<MyDelegate>
@end

#import "Caller.h"
@implementation Business
- (int)funWithArg:(int)arg1 {
    return arg1+1'
}
@end

被调用者需要调用代理对象:

// in my.m

int n = [deleage funWithArg:1];
  1. 释放代理对象时,要将被调用者的delegate属性设为nil
  2. 当使用多个同类对象时,代理机制本身没有提供区分的办法
    设想一个controller中添加了两个tableview,controller本身作为代理,代理方法只能使用同一套,要么使用tag作为区分,要么将delegate单独成类,编程都比较繁琐,因此,我们需要一种一对一的便捷方法。

Block

回顾一下方法调用,调用时可以指定参数值,并获取对应的返回值,是很单纯的一对一调用模式,如果我们将一些执行方法,“打包”成参数传入,是对这种一对一模式很自然的补充,这就是Block的本质:

Block允许将方法作为参数传递给方法

因此Block在执行那些一对一的交互任务时,显得得心应手,比如AFNetworking中处理网络请求的返回值和失败信息,无需担心多次网络请求会导致信息“互串”。
但由于Block解决了代理定义繁琐,一对多的问题,自然也带来了副作用:

Block本身是匿名函数,因此复用的办法只能是copy,这是编程大忌

另外Block的调用是有延迟的,这点往往会被初学者误解,如下面的代码:

Record *record = [[Record alloc] init];
[drivingRecord save:^{
    record.createTime = [NSDate date];
}];
NSLog(@"%@", record.createTime)

输出的createTime是nil !因为Block可能是使用异步线程调用的。

通知

NSNotification是系统Foundation提供的消息机制,使用起来是很直接的:

  • 观察者注册
  • 被观察者发消息
  • 观察者注销
    这是一种对消息发送者来说很“轻松”的消息机制,除了调用
    - postNotification:外,几乎不用做任何事情。

因为使用简单,因此通知也有一些天生的缺点:

  1. 一对多传递消息,消息发送者不能指定接收者。
  2. 通知发出后,被观察者不能从观察者获得任何的反馈信息,所以这不是一个双向传输的消息机制。
  3. 对观察者来说,调用和得到的结果并没有明显的对应关系,甚至不在同一个类中,调用关系不直观。
  4. - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;被调用多少次,回调就会被执行多少次,该特性往往导致一次post,多次调用;在ViewController中添加注销时,要注意添加和注销是否一一对应,错误往往在ViewController没有被正确释放时出现!

另外,还有些语法上带来的缺陷:

  1. 消息的主要由消息名称来区分,没有良好的编程习惯,很容易导致消息混淆,使观察者处理了不是发给自己的消息。
  2. 如果没有使用
    - postNotificationName:object: 的object参数告知消息发送者,观察者无法确定消息来源。

KVO

添加监听者:
- addObserver:forKeyPath:options:context:
所有监听值的变化都会由一个方法获取,必须重写父类的同名方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

主要缺陷:

  1. 所有返回结果都由一个方法处理,导致一连串的if判断,修改和阅读都不是十分清晰。如果父类也实现了observeValueForKeyPath...会进一步导致这种混乱。
  2. 由于addObserver... 无法指定单独的回调方法,单独为每次调用指定方法是不可能的。
  3. 事实上,正如KVO的名称所暗示的——键-值-监听——该机制只能适用于值的变化,尝试处理一些没有值(比如按钮事件),或一连串的相关事务时(比如网络下载时,先获取头,再根据头确定下载),用KVO实现就会变得非常臃肿,逻辑不清。
  4. 从代码清晰度角度看,KVO声明的接口是隐性的,与普通属性没有明显的区分(当然,你应该添加注释说明),除非编码尝试,调用者无法获知一个属性是否能够被监听。

Target-Action

最典型的就是UIButton的
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;

SEL中指定的方法是固定格式的
-(IBAction)doSometing:(id)sender
可以自己实现一个Target-Action机制,如下声明一个SEL:

SEL aSelector = @selector(methodName);

执行一个SEL:

SEL aSelector = @selector(run);
[aDog performSelector:aSelector];
[anAthlete performSelector:aSelector];

根据实现原理不难发现,Target-Action机制有以下缺陷:

  1. selector只能查找指定类(含子类)的方法。
  2. SEL 查找的方法不支持类方法
  3. 最大的缺陷就是不能传参数,唯一参数用于传递发消息的控件对象,因此所有传递的值必须放在指定对象中,也因为这个参数是可选的,较容易出错。
  4. 这也是一种单向传输的机制,执行SEL无法获得返回值。

综上所述,每种消息机制各有自己的适用情形,不存在谁比谁优秀先进的问题,一个复杂系统中往往是各种机制协同工作。

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 把网上的一些结合自己面试时遇到的面试题总结了一下,以后有新的还会再加进来。 1. OC 的理解与特性 OC 作为一...
    AlaricMurray阅读 2,472评论 0 20
  • 说明:面试题来源是微博@我就叫Sunny怎么了的这篇博文:《招聘一个靠谱的 iOS》,其中共55题,除第一题为纠错...
    __Lex阅读 628评论 0 4
  • two node sum to a target on a binary search tree. instant...
    秋_轩阅读 227评论 0 0