作者:雪山飞狐_91ae
链接:https://www.jianshu.com/p/4aeaf3aa0c7e
系统响应阶段
1、手指触碰屏幕,屏幕感受到触摸后,将事件交由IOKit来处理。
2、IOKIT将触摸事件封装成IOHIDEvent对象,并通过mach port传递给SpringBoard进程。
mach port是进程端口,各进程间通过它来通信。Springboard是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统接收到的触摸事件。
3、SpringBoard由于接收到触摸事件,因此触发了系统进程的主线程的runloop的source回调。
发生触摸事件的时候,你有可能正在桌面上翻页,也有可能正在头条上看新闻,如果是前者,则触发SpringBoard主线程的runloop的source0回调,将桌面系统交由系统进程去消耗。而如果是后者,则将触摸事件通过IPC传递给前台APP进程,后面的事便是APP内部对于触摸事件的响应了。
APP响应触摸事件
1、APP进程的mach port接收来自SpringBoard的触摸事件,主线程的runloop被唤醒,触发source1回调。
2、source1回调又触发了一个source0回调,将接收到的IOHIDEvent对象封装成UIEvent对象,此时APP将正式开始对于触摸事件的响应。
3、source0回调将触摸事件添加到UIApplication的事件队列,当触摸事件出队后UIApplication为触摸事件寻找最佳响应者。
4、寻找到最佳响应者之后,接下来的事情便是事件在响应链中传递和响应。
触摸 事件 响应者
触摸
触摸对象即UITouch对象。
一个手指触摸屏幕,就会生成一个UITouch对象,如果多个手指同时触摸,就会生成多个UITouch对象。
多个手指先后触摸,如果系统判断多个手指触摸的是同一个地方,那么不会生成多个UITouch对象,而是更新这个UITouch对象,改变其tap count。如果多个手指触摸的不是同一个地方,那就会生成多个UITouch对象。
触摸事件
触摸事件即UIEvent。
UIEvent即对UITouch的一次封装。由于一次触摸事件并不止有一个触摸对象,可能是多指同时触摸。触摸对象集合可以通过allTouches属性来获取。
响应者
响应者即UIResponser
下列实例都是UIResponser:
UIView
UIViewController
UIApplication
Appdelegate
响应者响应触摸事件是通过下列四个方法来实现的:
//手指触碰屏幕,触摸开始
- (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;
寻找最佳响应者(Hit-Testing)
当APP通过mach port得到这个触摸事件时,APP中有那么多UIView或者UIViewController,到底应该给谁去响应呢?寻找最佳响应者就是找出这个优先级最高的响应对象。
寻找最佳响应者的具体流程如下:
1、UIApplication首先将
事件传递给窗口对象(UIWindow),如果有多个UIWindow对象,则先选择最后加上的UIWindow对象。2、若UIWindow对象能响应这个触摸事件,则继续向其子视图传递,向子视图传递时也是先传递给最后加上的子视图。
3、若子视图无法响应该事件,则返回父视图,再传递给倒数第二个加入该父视图的子视图。
[图片上传失败...(image-95c4b4-1523776714398)]
例如上面这张图,C在B的后面加入,E在F的后面加入。那么寻找最佳响应者的顺序就是:1、UIWindow对象将事件传递给视图A,A判断自己能否响应触摸事件,如果能响应,则继续传递给其子视图。
2、如果A能响应触摸事件,由于A有两个子视图B,C,而C又在B的后面加入的,所以A视图再把触摸事件传递给C,C再判断自己能否响应触摸事件,若能则继续传递给其子视图,若不能,则A视图再将触摸事件传递给B视图。
3、如果C能响应触摸事件,C视图也有两个子视图,分别是E和F,但是由于E是在F之后加到C上面的,所以先传递到,由于E可以响应触摸事件,所以最终的最佳响应者就是E。
视图如何判断自己能否响应触摸事件?
下列情况下,视图不能响应触摸事件:
1、触摸点不在试图范围内。
2、不允许交互:视图的userInteractionEnabled = NO。
3、隐藏:hidden = YES,如果视图隐藏了,则不能响应事件。
4、透明度:当视图的透明度小于等于0.01时,不能响应事件。
寻找最佳响应者的原理
hitTest:withEvent:
每个UIView都有一个hitTest:withEvent:方法。这个方法是寻找最佳响应者的核心方法,同时又是传递事件的桥梁。它的作用是询问事件在当前视图中的响应者。hitTest:withEvent:返回一个UIView对象,作为当前视图层次中的响应者。其默认实现是:
若当前视图无法响应事件,则返回nil。
若当前视图能响应事件,但无子视图可响应事件,则返回当前视图。
若当前视图能响应事件,同时有子视图能响应,则返回子视图层次中的事件响应者。
开始时UIApplication调用UIWindow的hitTest:withEvent:方法将触摸事件传递给UIWindow,如果UIWindow能够响应触摸事件,则调用hitTest:withEvent:将事件传递给其子视图并询问子视图上的最佳响应者,这样一级一级传递下去,获取最终的最佳响应者。
hitTest:withEvent:的代码实现大致如下:</