研究了一下iOS开发的响应链,这里做一下笔记。笔记的内容基于个人的理解,若有谬误之处有待后续不断学习更新改正。做一个尽量完美的记录是对所学知识的总结,也是一种升华。
响应链是iOS应用处理与用户交互事件的机制。所谓的交互事件包括触摸屏幕、晃动手机等。开发iOS所用的Objective-C编程语言是面向对象的语言,对于事件的响应是由对象完成的,但并不是所有的对象都能对事件做出响应,只有继承 UIResponder的类才能对事件做出响应。
在UIResponder类中以下几个方法是与处理触摸事件有关的方法。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);
在上面几个方法的参数中都有个
(NSSet<UITouch *> *)touches,其中包含的是触摸的信息,包括触点位置与触点个数等,默认只包含一个触点,如果想要处理多点触摸事件,需要把UIView的multipleTouchEnabled属性设置为YES,否则此视图只接受多点触摸时第一个触点的信息。 其中的(UIEvent *)event参数表示一次与用户的交互事件,包含事件发生时间、事件类型、此事件所关联的触点等信息。
如果想要一个视图对触摸事件做出响应,那么需要它重写上述方法。
iOS开发中有三种继承UIResponder的类,他们都能对交互事件做出响应。
事件传递链
既然继承UIResponder的类都能对触摸事件做出响应,那么当触摸事件发生时究竟要谁来处理呢?直觉告诉我点谁就让谁处理呗!但是这只是我们人类的直观感觉,计算机并不能像人类一样直观地看到用户点击的是哪个视图。在应用中如何找到用户点击的视图是有一套流程的。这套流程被形象地称为事件的传递链。
在iOS中,每个应用都会有一个UIApplication类,应用启动时,系统会调用此类的主函数,之后应用会创建一个UIWindow来作为应用的主窗口(keyWindow),UIWindow会将某个视图控制器UIViewController作为其根视图控制器,然后视图控制器有自己所管理的视图UIView,通常我们会在UIView上添加各种视图控件,比如UIButton。整体的结构如下图。

所以,当一个触摸事件发生时,系统会将这一事件加入到一个由UIApplication管理的事件队列的尾部,等待得到处理。当其前面的事件处理完成,此事件会被UIApplication分发给UIWindow,之后UIWindow会调用hitTest:thePoint方法,此方法会首先判断该视图能否响应触摸事件,如果不能响应那就直接返回nil;如果可以响应触摸事件,那就调用pointInside: withEvent:方法判断触点是否在该视图范围内,如果不在则返回nil,如果触点在视图范围内则向此视图的所有子视图发送hitTest: thePoint:消息。
所有子视图的遍历顺序是从栈顶到栈底,直到有子视图返回非空或者所有子视图全部返回为nil。如果一个视图没有子视图或者所有子视图hitTest: thePoint:都返回nil,则此视图的hitTest: withEvent:返回自己。
就这样一层层调用下去,直到有一个视图的hitTest: withEvent:返回自身,那么也就说UIKit找到了用户点击的是哪个视图。
当UIView中的 isUserInteractionEnabled = NO、isHidden = YES、alpha <= 0.01时,
hitTest: withEvent:方法返回nil
事件响应链
通过上面的事件传递链已经找到了用户点击的是哪个视图,那么具体怎么对触摸事件做出响应呢?
首先会判断用户点击的视图能否处理此触摸事件,如果能处理则处理触摸事件;如果不能处理则UIKit将触摸事件传递给父视图,重复此过程一直传递到所在的视图控制器的根视图,如果根视图不能处理,则传递给所在的视图控制器,如果视图控制器不能处理,则再传递给根视图的父视图。。。
直到传递给窗口(window),如果窗口还不能处理,则传递给UIApplication,如果UIApplication也不能处理则还可能传递给App Delegate(如果App Delegate是UIResponder类)。
整个过程如下图
更改响应链
我们也可以通过重写nextResponder属性来更改响应链。如果我们不去更改此属性值,UIKit会按照默认的规则给此属性赋值。赋值规则是:
- UIView:如果视图是某个视图控制器的根视图,那么它的
nextResponder属性就是这个视图控制器,否则就是视图的父视图。 - UIViewController:
- 如果一个视图控制器的视图是窗口的根视图,那么它的下一个响应者就是窗口
- 如果这个视图控制器是由另一个视图控制器
present出来的,那么它的下一个响应者就是另一个视图控制器。 - UIWindow:下一个响应者是
UIApplication对象 - UIApplication:如果App Delegate是
UIResponder类且不是UIView、UIViewController或者应用本身,则其下一个响应者是App Delegate
REFERENCE
