首先是对事件回掉的定义简单的介绍, 以及在iOS平台下事件回调的几种方式.
名词定义
事件
由用户操作、具有逻辑的模型、远程的网络响应产生的、由程序员无法控制触发时间的代码执行点。
举例
用户操作:用户单击一个Button
逻辑模型:AVAudioPlayer播放完毕
远程响应:下载进度改变,获得Data
回调
程序员在不知道事件触发点的情况下,写好事件触发后应有的响应和事件处理,并且交给操作系统来监控事件和作出响应处理事件
举例
[UIButton addTarget:action:event] [AVAudioPlayer.delegate audioPlayer:didPlayCompleted:(BOOL)succeed] [NSURLSession.delegate URLSession:dataTask:DidReceiveData:]
事件回调的方式
- delegate
- block
- NSNotificationCenter
- KVO (Key-Value-Observer)
- Target-Action
先定义两个词:
Event Source 事件源
产生事件的对象, 比如:UIButton, AVAudioPlayer, NSURLSession
Event Handler 事件处理者
处理某个事件的对象, 比如:UIViewController.
delegate 代理
说明
事件源: 有个属性delegate, 该delegate实现了一些特定的方法, 为了保证一些方法的存在, 和定义响应方法的方法名, 通常会有一个代理协议.
事件处理者: 实现了代理协议, 并实现了协议中所需要的方法, 方法中写处理事件的代码.
当事件产生, 事件源就会向他的delegate发送消息, 如果消息响应, 就会执行事件处理者(delegate)中的方法, 从而实现事件回调.
特点
一、一个事件处理者通常可以处理多个事件源发出的事件
该事件处理者可以成为多个同类对象的delegate, 并且只需要实现一次delegate方法即可完成多个对象的事件处理.
比如UIViewController实现了UITextFieldDelegate, 则这个vc即可通过一个代理来处理所有的textField对象发出的消息.
优点: 可以减少事件处理实现所需要的方法个数
缺点: 如果不同对象的处理事件的方法不同, 需要协议方法中自己判断事件源.
二、一个事件源通常只有一个delegate, 即只有一个事件处理者
该事件源只能向一个delegate发送事件产生消息. 无法向多个对象发送事件产生消息.
比如UITableView只能有一个dataSource和一个delegate. 重复设置则会覆盖上一次的设置.
优点: 接收事件的对象唯一, 并且拥有该对象的引用, 对于事件源来说是一一对应的, 所以一旦bug出现, 可以立马找到事件处理者(继续调试bug).
缺点: 只能有一个事件处理者. 由于只有一个事件处理者, 所以如果事件源是个单例, 则需要不断修改delegate来改变事件处理者.
[从(一)(二)看出delegate是”多对一"关系]
三、一般delegate中协议方法都有返回值
最常见的就是UITableViewDataSource, 每一个方法都有一个返回值.
优点: 在非事件回调场景中, 可以实现对一个对象的定制.
缺点: 没有(不在同一个层面, 没有缺点可谈)
四、一般一个delegate协议中拥有多个方法, 并且拥有”过程性质"
delegate偏向与一个事件的处理过程, 就是对一个事件的将要产生, 正在发生, 事件结束过程的处理.
如NSURLSessionDelegate, 其拥有接收到Response, 接收到Data, 接收到Error, 下载进度, 下载完毕.
从这些方法即可看出, 一个delegate的方法就是对一个事件的不同时间的处理.
再如UITextFieldDelegate, 有TextField能否编辑, 将要编辑, 已经开始编辑, 内容改变, 能否返回, 将要返回, 已经返回. 也是一系列随着时间变化的处理方法过程.
block 闭包
说明
事件源: 有一个属性block, 存放要处理事件的代码, 该代码虽然是以属性的形式存在与事件源, 但是代码的内容是由事件处理者产生, 并将整段代码传给事件源.
事件处理者: 实现一段代码, 将这段代码以参数(或者属性)的方式传给事件源. 在事件发生后事件源无需再通知事件处理者, 自己执行那段代码.
特点:
一、一个事件处理者可以处理多个事件源发出的事件
该特点表面与delegate一样, 但是底层仍旧有很多差别
处理事件的代码是多个, 而不是一个
在block回调方法中, 事件处理者必须为每一个事件源传递处理事件的代码, 每段代码均是相对独立存在的对象, N个事件源就会对应N段事件处理代码, 也就是N个处理对象. 会占用内存.
在delegate回调方法中, 事件处理者只需要写一段事件处理的代码, 可供多个事件源使用, N个事件源对应1个事件处理代码, 也就是1个delegate.事件源无法知道谁是处理者, 只能知道处理者干了什么
在block回调方法中, 事件源只能得到事件处理者的”处理任务”, 但是无法得到这个任务到底是谁给它的, 也就是说并没有一个引用指向处理者.
在delegate回调方法中, 事件源拥有一个属性delegate, 可以通过delegate得到事件处理者.
优点: 对不同对象的处理定制
缺点: 处理代码块容易占用内存.
二、一个事件源可能动态持有block
该特点与delegate的(二)相反, 一个事件源在不同时间发出类似事件, 可以有不同的处理block.
[PS: 动态持有代表block可以改变, 而不是”一对多”的关系].
下面举一个小新老师教的例子:
在BaseNetwork的子类中, 有不同的”GET”开头的类方法, 每个方法都有block. 每次AFN得到请求后, 都会调用block, 从而实现一个方法满足不同环境的需要, 也就是不同的处理代码.
三、一般block都没有返回值
这个显而易见, 你写的block中并没有return.
四、一般只传一个到两个block给事件源, 并且拥有”结果性质”
这个”结果性质”是相对于delegate的”过程性质”而言.
定义block时, 通常用completed, completedHandle, failed, error等字眼, 表示这个事件结果是成功, 还是失败.
不难看出, block只是在一个事件结束后, 对这个事件的结果处理. 与delegate比较, 没有过程的性质存在.
比如AFN中, GET请求方法离, 只需要传递成功后的block, 和失败后的block, 不需要考虑在请求过程得到的Response和Data.
NSNotificationCenter 通知中心
说明
事件源: 通过系统共用的通知中心发送一个通知
事件处理者: 向系统共用的通知中心注册需要接收的通知名称, 在通知被事件源发出后, 系统会自动发送给所有注册该通知的事件处理者.
事件源不需要关心事件处理者的个数, 只需要发送事件发生的通知即可. 事件处理者也不需要关心谁发出来的事件, 只需要处理事件即可.
特点
一、一个通知可以由任何对象发出
一般只由一个对象发出.
二、一个通知可以有很多个接收者
自己写的通知接收者一般只有一个或多个, 系统发出的通知的接收者一般有多个.
从(一)(二)点看出, 事件源和事件处理者是”一对多"的关系.
三、方便
事件源和事件处理者的跨度很大
事件源不需要得到事件处理者的引用, 事件处理者也不需要得到事件源的引用.
优点: 随时随地都可以使用
缺点: 如果很多通知, 很多接受者, 很多发送者, 然后出现了bug, 然后就没有然后了...
KVO (Key-Value-Observer) 观察者
说明
事件源: 自己的某个属性被修改了, 则告诉监控自己的Observers.
事件处理者: 添加一个针对事件源的某个属性的Observer, 监控该事件源的属性.
事件源任何时间改变自己的一个Key对应的Value(也就是属性的改变), 就会通知Observer. Observer接收到消息后便会调用设定的方法.
特点、
一、同上, 也是”一对多”的关系
二、观察者模式非常占用系统资源
优点: 很简单的实现KV监控, 无需开线程和Timer.
缺点: 占用大量的系统资源
慎用.
Target-Action 目标行动
说明
事件源: 拥有Target-Action列表,
事件处理者: 向事件源的Target-Action列表注册事件, 以及target(self)的一个action(SEL).
事件源发生事件后, 向Target-Action列表发送事件消息.
特点
一、支持“多对多”, 这种情况很少用
二、UIControl中大量使用
系统自带的UIControl均使用这种模式回调
三、在非UIControl中自己实现Target-Action
类似delegate, 但是与delegate最大的区别就是不需要协议来限制方法名.
用这种方法的话, 回调时候传参中, 除了第一参数是self(事件源引用), 最好不要有第二个参数. 因为事件源只能知道参数个数, 不能知道参数的类型.
还要注意的是Target-Action的数组, 实现这个数组很麻烦.
所以自己定义的话最好是setTarget:Action:而不是addTarget:Action:, 这样的结果就是”多对多”变成”一对一”.
那么, 问题来了...
这么多的回调方式, 在实际开发中如何选择?
首先必须要明确的一点: 如果一个事件的事件源和事件处理者是”一对一”的关系, 以上5个回调方式全部都可以实现.
既然都可以实现, 那就可以随便选择了?
理论如此, 但是最好酌情选择, 具体情况具体分析.
从优先级排序的话, 可以这么排
- block 处理事件结果, 代码简洁美观, 基本的回调功能都有, 满足各种情况的回调需求.
- delegate 如果出现需要返回值, 有过程性质的存在, 可以代替block.
以下回调方式在上面无法实现的情况下去使用
- NSNotificationCenter 1和2都不能实现一对多, 在这种情况下去使用.
- Target-Action 自己很少实现, 一般使用系统自带的.
- KVO 慎用.