当我们点击屏幕时,iPhone OS
获取到了用户"单击"这一行为,操作系统把包含这些点击事件的信息封装成 UITouch
和 UIEvent
形成的实例,然后找到当前运行的程序,逐级找到能够响应这个事件的对象,知道没有响应者响应。这一寻找的过程称为响应链
。
响应者
在 iOS 中,能够响应事件的对象都是 UIResponder 的子类对象。
UIResponder 提供了用户点击的四个回调方法,分别对应点击、移动、点击结束和取消点击,其中取消点击只有在程序强制退出或来电视才会调用。
响应链传递
系统时怎么通过用户点击的位置找到处理点击事件的 view ?
上文说过系统通过不断查找下一个响应者来响应点击事件,而所有的课加护空间都是 UIResponder 直接或者间接的子类,那么我们是否可以在这个类的头文件中找到关键的属性呢?正好存在这么一个方法 - (nullable UIResponder*)nextResponder;
,通过方法名不难获取当前 view 的下一个响应者,那么我们重写 touchesBegan
方法, 逐级获取下一个响应者,直到没有响应者位置。
相关代码如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIResponder * next = [self nextResponder];
NSMutableString * prefix = @"".mutableCopy;
while (next != nil) {
NSLog(@"%@%@", prefix, [next class]);
[prefix appendString: @"--"];
next = [next nextResponder];
}
}
运行结果:
AView
--UIView
----ViewController
------UIWindow
--------UIApplication
----------AppDelegate
虽然结果非常有层次,但是从系统逐级查找响应者的角度上来说,这个输出的顺序是刚好相反的。为什么会出现这种问题呢?我们可以看到输出中存在一个ViewController类,说明UIViewController也是UIResponder的子类。但是我们可以发现,controller是一个view的管理者,即便它是响应链的成员之一,但是按照逻辑来说,控制器不应该是系统查找对象之一,通过nextResponder方法查找的这个思路是不正确的。
后来在 UIView 中发现这两个方法,分别返回 UIview 和 BooL 类型的方法:
一个方法是返回响应点击事件的对象,
另一个是根据点击坐标返回事件是否发生在本视图内。
响应者栈:
所有响应者都是在查找中返回可响应点击的视图,因此可以推测,UIApplication 对象维护这自己的一个响应者栈,当 pointInside:withEvent: 返回 YES 时候,响应者入栈。
栈顶作为最先响应的对象,如果 AVView 不处理事件,那么出栈,移交给 UIView,依次下去,知道事件得到了处理或者到达 APPDelegate 后依旧未响应,事件被摒弃为止。