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

iOS开发基础:消息传递机制的不同与缺陷_第1张图片
图片发自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 deleage;
}
@property(assign,nonatomic) id delegate;
@end

调用者需要实现该协议:

@interface Caller : NSObject
@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无法获得返回值。

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

你可能感兴趣的:(iOS开发基础:消息传递机制的不同与缺陷)