当手指触摸到屏幕时, 一个触摸事件就在系统中产生了.通过进程间通信(IPC), 最终传递给合适的应用,并找到应用中的最佳响应者
进行响应. 整个流程大概如下:
1. 系统响应阶段
- 当手指触摸到屏幕, 屏幕感受到触摸后, 系统会将触摸交给
IOKit.framework
处理.IOKit.framework
在内部会将触摸封装成一个IOHIDEvent
对象. 并且通过mach port
递交给SpringBoard.app(系统桌面)
进程
mach port 进程端口,各进程之间通过它进行通信。
SpringBoad.app 是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统接收到的触摸事件。
SpringBoard
进程收到这个IOHIDEvent
对象, 会触发SpringBoard .app
进程中的主线程runloop
的source1
事件回调. 此时SpringBoard .app
会根据桌面状态, 判断是否有运行在前台的进程. 若无app
在前台运行, 那么会触发SpringBoard .app
进程主线程runloop
的source0
回调, 事件由SpringBoard
进程内部消耗. 若有app
在前台运行, 则SpringBoard
通过mach port
将IOHIDEvent
分发给该app
. 接下来就是该app
内部消耗这个IOHIDEvent
对象的过程.app
接受到这个IOHIDEvent
对象, 其主线程的runloop
被唤醒, 其中的source1
回调被触发, 并触发source1
中的source0
回调, 在这个source0
回调中,IOHIDEvent
对象被封装成为一个UIEvent
对象. 此时app
正式开始对事件的响应.source0
回调将这个UIEvent
对象添加到给UIApplication
对象的事件队列中. 事件出队后,UIApplication
开始一系列寻找最佳响应者
的过程, 称为Hit-Testing
.
2. Hit-Testing
- 从逻辑上来讲,
探测链
机制是最先发生的. 当产生触摸事件后, iOS系统会通过Hit-Testing
来找到最佳响应者
, 去响应这个触摸事件. 只要用到了UIView
的两个方法:
// recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// default returns YES if point is in bounds
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
前者会通过recursively calls
递归调用来返回一个适合响应触摸事件的UIView
对象:
如果通过demo来验证
Hit-Testing
, 会发现, 其实整个Hit-Testing
过程会发生两次. 对此苹果官方有相应的回复:Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.
UIApplication
通过Hit-Testing
找到了最佳响应者并且遍历得到其所有的UIGestureRecognizer
, 然后根据最佳响应者
、UIGestureRecognizer
、UIWindow
创建UITouches
对象, 并将其保存在UIEvent
中.UIApplication
将UIEvent
发送给UIWindow
,UIWindow
首先将事件发送给UIGestureRecognizer
, 然后发送给最佳响应者
, 事件沿Responder Chain
传递.UIGestureRecognizer
开始识别自己的gesture
, 当某个gestureRecognizer
识别成功后,UIGestureRecognizer
将独占所有需要的UITouches
, 识别成功的gesture
所关联的UITouch
中的hitTest view
收到-touchesCancelled()
消息, 并且此后该hitTest view
不再收到该UITouch
事件target: action:
触发,action
方法内部实现相应的触摸事件响应.事件处理完成, 若runloop
无其他事件处理, 则runloop
进入休眠, 等待下一个事件到来.识别成功的
gestureRecognizer
所关联的UITouches
的其他所有gestureRecognizer
收到-touchesCancelled()
消息, 此后不再收到该UITouch
事件.如果没有识别成功的
gestureRecognizer
, 那么hitTest view
将不会收到-touchesCancelled()
消息. 若有最佳响应者
能够处理该事件. 那么事件处理完成, 若runloop
无其他事件处理, 则runloop
进入休眠, 等待下一个事件到来.若
最佳响应者
没能够处理该事件, 则进行下个步骤:Responder Chain
3. Responder Chain
通过Hit-Testing
找到的视图拥有最先对触摸事件进行响应的机会. 如果这个视图无法处理触摸事件, 就会沿着Responder Chain
向上进行传递.直到找到可以处理事件的视图, 或者一直到UIApplication
都没有能够处理, 该事件就会被废弃掉. 若runloop
无其他事件, 则进入休眠, 等待下一个事件到来.