响应链 --- 被很多人忽视的另一种数据交互方式
前言
这篇文章有些是借鉴了其他博客文章的知识点加以理解的,在最下方我会列出参考的链接。有兴趣的可以点进去看下。
主要内容
本篇文章的主要内容有响应链的构成、响应链的传递过程、hit:test判定流程、通过响应链进行交互传递。
1.响应链的构成
从名字中开始理解,响应链是由一系列响应者通过某种联系构成的链条。
-
响应者:具有响应和处理事件能力的对象。基类是UIResponder=
-
链条连接器:pointInside 和hit:test方法
-
响应者类关系图
响应者类关系图.png
2.响应链的传递
当界面上的一个button被点击时
- 1.系统会封装一个UIEvent对象传递到Appdelegate
- 2.由Appdelegate向Application、UIWindow等向上传递事件
- 3.视图收到父响应者传递的事件后,通过hitView判断是否选中
- 4.如果没有判定中,那么不处理,如果判定中,遍历子视图,递归执行3和4
- 5.最后递归找到最顶层的button,最适合的视图
- 6.如果能响应,执行它的交互响应 ,不能的话,递归到上一个响应者,执行响应 。
- 7.如果一直递归到appdelegate到都没响应,则废弃
不能响应的原因 :
- view.hidden = YES
- view.alpha<0.01
- view.userinterface = NO
- 不添加交互事件并不是不能响应,只是默认不做处理
3.hit:test流程
刚才我们说到响应者需要将事件在响应链上传递,在内部是通过两个方法进行传递 pointinside 和hit test
1.首先对响应链最底层的View(UIWindow)视图进行命中判定,调用hittest:withevent方法
2.如果判定不是该视图,则返回nil,pointInside:withevent方法无效,如果命中,则根据pointInside, 返回是不是响应区域,如果是,则遍历该视图的子视图进行hittest判定
3.重复2,知道找到最上层最合适的视图,然后执行事件
假设用户点击了视图E
1.首先touch会命中window,查出上级视图是viewA
2.viewA判定在A中,然后向上遍历子视图B和C
3.B判定不在自己的视图内,返回nil,B分支结束
4.C判定在自己的视图内,向上遍历D和E进行判定
5.D判定不在视图内,D分支结束
6.E判定在该视图内,子视图为nil,则E是最合适视图
- E进行响应,如果E无法响应,则执行父视图C的响应
以上就是响应链传递的流程,说完了流程现在来说说响应链具体有何应用
应用一: 超区或者裁区响应
image.png对于这张图片,一般简单做法就是方形视图切圆角,这样的话圆形周边的四个角(红色区域)也会响应,如果交互或者产品要求只能圆形响应的话,这时候就需要重写方法
#import "EDHSqurView.h"
@implementation EDHSqurView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
UIView *vuew = [super hitTest:point withEvent:event];
return vuew;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
//已经知道是个正方形
CGFloat radius = self.frame.size.width/2;
UIBezierPath *bezier = [UIBezierPath bezierPathWithArcCenter:CGPointMake(radius, radius)
radius:radius
startAngle:0 endAngle:M_PI*2
clockwise:1];
return CGPathContainsPoint(bezier.CGPath, NULL, point, NO);
}
@end
这段代码要用到的是下面那个方法,进行裁剪交互区域,返回可交互的圆形区域
按钮超区.png这段图片也是进行超区处理
应用二:通过响应链进行对象间交互
目前常用的数据/事件回传有block、delegate、notification,各有所长。响应链具有逐层传递的特点,可通过nextresponder取到下一个响应者,基于这个特点,可以设计通过链路进行对象的数据回传
具体做法
- 1.通过UIResponder的一个category,定义一个方法,通过响应链向底层传递
- 2.通过extern nsstring const 方式定义交互事件的eventname名字(也可以硬编码)
- 3.以tableview为例,cell上有多个交互事件,在button的点击事件里调用category方法
-
4.在controller里面接收到上一层响应者发来的信息,处理,若需要继续传递(或穿透传递) 则调用category的方法
1-定义分类
2-定义eventname
3-在交互事件是执行category
4-在控制器里的操作
下层响应者接受多个响应事件的strategy模式
当下层响应者例如controller接受来自多个子视图的事件时,会导致判断的if else冗余,这时可以采用strategy对每个eventname分类处理
解决办法:定义一个字典,eventname做key,invocation做value
在routeEvent里面通过eventname取出strategy字典的value - invocation,
然后[invocation invoke]
这种方式要求每个eventname都对应一个selector,如果event不是很多的话,
用if else 也没什么关系
如下图
定义event字典 事件调用的地方
-
响应链传递和代理block等的使用对比