iOS 事件机制 -- 从触发到响应
当手指触摸到屏幕时, 一个触摸事件就在系统中产生了.通过进程间通信(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
无其他事件, 则进入休眠, 等待下一个事件到来.