回调方式总结
今天对ios开发中常用的几种回调方式:block、delegate与NSNotification进行一下总结,并对在实际开发中该如何选择。在我们日常的开发任务中,我们经常要用到一些回调的手段,譬如说网络请求操作。
先说KVO吧,它是cocoa框架实现的观察者模式,一般同KVC搭配使用。通过KVO可以监测一个值的变化,比如View的frame变化,一个Person模型的属性字变化等。是一对多的关系,一个值的变化会通知所有的观察者。KVO一般的使用场景是数据,需求是数据变化,比如实时商品价格变动,一般使用KVO(观察者模式)。
//新建一个person类
@interface Person : NSObject
@property (nonatomic,copy) NSString * name;
@end
//ViewController中new一个Person然后它监听
@interface ViewController ()
@property(nonatomic,strong) Person * p ;
@end
- (void)viewDidLoad {
[super viewDidLoad];
_p= [[Person alloc]init];
_p.name = @"番茄";
[_p addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"输出了旧值与新值");
}
-(void)changePersonAction{
//改变了,值会出发上面监听方法
_p.name = @"bu ai le";
}
NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是相似的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。
//创建一个监听
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationTest) name:@"notificationTest" object:nil];
-(void)notificationTest{
NSLog(@"收到通知了");
}
//主动打出通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"notificationTest" object:nil];
delegate 是代理,其存在的意义就是我不想做的事情交给别人做,我告诉你我要做什么,但是具体怎么做,那是你的事了。比如 A类——>B类,B不想做的事情,就交给A类处理,A类要遵循B的delegate就可以了,其他就由A类完成所需要的操作。所以delegate是一对一关系。
BViewController类
//定义代理
@protocol BDelegate <NSObject>
-(void)getACCName:(UIColor*)clolro;
-(void)testAction1;
-(void)testAction2;
@end
//声明代理
@interface BViewController : UIViewController
@property (nonatomic,weak) id<ADelegate> delegate;
@end
//respondsToSelector 判断方法是否实现了,实现了就去调用,防止出现异常,造成crash
UIColor * redC = [UIColor redColor];
if(_delegate && [_delegate respondsToSelector:@selector(getACCName:)]){
[self.delegate getACCName:redC];
}
if(_delegate && [_delegate respondsToSelector:@selector(testAction1)]){
[self.delegate testAction1];
}
if(_delegate && [_delegate respondsToSelector:@selector(testAction2)]){
[self.delegate testAction2];
}
############AViewController类###############
UIStoryboard * sb = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
BViewController * av = [sb instantiateViewControllerWithIdentifier:@"BViewController"];
av.delegate = self;
[self.navigationController pushViewController:av animated:YES];
//实现代理事件
-(void)testAction1{
NSLog(@"触发了testAction1");
}
-(void)testAction2{
NSLog(@"触发了testAction2");
}
block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观,可以追溯事件过程。如果没有block,有可能出现一个类需要使用N多其他类时,要遵循N多个delegate,实现N多个delegate方法的情况,逻辑到处散落,难以理清。但也不是说block就可以完全替代delegate,,要看情况结合使用才是王道。
//举例把 block 声明成属性
typedef void(^MyBlcok)(int a);
@interface BViewController ()
@property(nonatomic,copy) MyBlcok myBlock;
@property (nonatomic,copy) void(^youBlock)(void);//等同于上面两个的结合
@end
############在BViewController.h中声明成属性###############
@interface BViewController : UIViewController
@property (nonatomic,copy) void(^youBlock)(void);
@end
//这种就完成类似delgate的功能
UIStoryboard * sb= [UIStoryboard storyboardWithName:@"Main" bundle:nil];
BViewController * av = [sb instantiateViewControllerWithIdentifier:@"BViewController"];
av.delegate = self;
av.youBlock = ^{
//事件响应
};
[self.navigationController pushViewController:av animated:YES];
//block作为参数
//封装网络请求
-(NSURLSessionTask*)GET:(NSString*)url parameter:(id)parameter singleResultCompletionBlock:(void(^)(NSDictionary * result,NSError * error))block;
KVO
优点
- 能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步
- 能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SDK对象)的实现
- 能够提供观察的属性的最新值以及先前值
- 用key paths来观察属性,因此也可以观察嵌套对象
- 完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点:
- 我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查
- 对属性重构将导致我们的观察代码不再可用
- 复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向
Delegate
优点:
- 非常严格的语法,非常具有条理,代码可读性较强,所有能响应的事件必须在协议中有清晰的定义
- 使用delegate的时候,控制连清晰明了,控制流程可跟踪和识别,一旦出现问题,可以比较方便的定位错误代码
- 在一个controller中可以定义多个协议,满足自定义开发需求,可选必选有较大的灵活性
- 能够接受调用的协议方法的返回值,并且减少代码的耦合性,使事件触发和事件响应相分离
- delegate相对方法来说,可以是一对多,一个delegate可以定义多个方法。
缺点:
- 需要定义很多代码,从上面的代码量就可以看出:(协议定义、controller的delegate属性、在delegate本身中实现delegate方法定义,实现委托的代码过程比较繁琐)
- 当实现跨层传值监听的时候将加大代码的耦合性,并且程序的层次结构将变的混乱,多个对象间通信时不太实用
适用场景:UI响应事件,回调方法(网络请求)
Block
优点:
- block书写简单,代码可读性比delegate 的更强,block 回调方法一般都跟随在声明之后,可以很快确认回调来源
- block注重结果的传输,只想知道成功或者失败,并不需要知道过多的额外信息
缺点:
- 容易造成就循环引用
注:使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。解决Block循环引用问题:当在大括号中出现self或者下划线的属性,可能造成死循环__weak typeof (self) weakSelf=selftypeof( )会自动识别括号中的对象类型
- block嵌套太多时,可读性变的非常差。
- delegate运行成本低,block的运行成本高
block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
- 相对于方法来说,一个block一个方法,假如每个方法都设置一个 block, 这样会很麻烦。方法过多时,delegate更优秀,自定义多个方法,只需要设置一次,就可以多次回调。
在事件处理方面:代理更加注重事件的过程:如发起一个网络请求,可能想要知道此时请求是否已经开始、是否收到了数据、数据是否已经接受完成、数据接收失败,常见使用UIWebview的代理事件(开始加载,加载中,加载完成.........)。而Block更注重事件处理的结果:比如对于一个事件,只想知道成功或者失败,并不需要知道进行了多少或者额外的一些信息。
适用场景:网络请求
Notification:
优点:
- 代码量少,实现比较简单
- 一个对象发出的通知,多个对象能进行反应,一对多的方式实现很简单
- 传值方便快捷,Context自身携带相应的内容
缺点:
- 编译期不能判断通知是否能被正确处理
- 释放注册的对象时候,需要在通知中心取消注册,否则将出现不可预见的crash
- 调试的时候,程序的工作以及控制流程难跟踪
- controller和观察者需要提前知道通知名称,如果定义不相同,会出现不同步的情况
- 轮询机制耗资源; 不易维护; 需要第三方对象来作为监听和被监听的对象
- 通知的效率比起delegate略差
适用场景:跨层通信,需要实现在两个毫无关联的对象之间的通信。
注:我们都知道,通知和定时器,我们需要手动释放,否则会造成内存泄漏,我们看到网上有说通知的释放可以在dealloc方法中进行释放,这个是没有错的,但是在实际的开发中,我们在不同的实际需求,释放的方法是不一样的。例如:现在A界面是在不断的接受通知。此时A跳到B界面,而在B界面中不需要A通知的事件,如果在A界面中将通知的释放写在dealloc中,跳到B界面,不会执行A界面中的dealloc方法。所以A界面仍然不断的接收着消息。所以这种需求,就应该讲A中的通知释放写在viewDidDisapper方法中。
总结:使用场景对比
1.回调方法:在日常的开发过程中,我们经常会遇到一些完成之后的处理问题,比如完成网路请求之后的回调,或者页面加载完成之后的回调等。这个时候我们一般使用的是前两者方法,即Block或者Delegate。而在一对一传输回 调的时候明显Block的使用更加的简单高效,只需要在代码块中执行所需要的操作即可。在一对多的情况下,Delegate更加能够发挥出自己的优势。
2.跨层通信 :有的时候我们需要实现在两个毫无关联的对象之间的通信,这个时候如果使用Block或者Delegate就势必会增加代码的耦合性,这样对于代码的结构来说是不健康的,因此这个时候使用Notification便是明智的选择。
3.UI响应事件 :用户在于App的UI进行互动的时候,总会需要App进行交互响应,这个时候就毫无疑问的使用代理设计模式。而苹果官方给出的建议也是可以肯定的,在Cocoa Touch框架中我们也可以在几乎所有的UI交互控件的头文件里看到Delegate的成员变量,也正是印证了在UI响应事件上Delegate有着绝对的优势。
4.简单值得传递 :当需要进行简单值得传递的时候,比如子控件传输给父控件所点击的IndexPath的时候,更加适合使用Block来传值。因为,如果只是为了传这一个简单的值而没有特别的业务处理而定义一个协议,然后实现协议,设置代理再写方法的话将十分麻烦,得不偿失,这个时候简单高效的Block就可以完美的替代Delegate完成任务了。