iOS 事件传递机制与响应链

iOS 事件类型

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, UIEventType) {
UIEventTypeTouches, //触摸事件
UIEventTypeMotion, // 运动事件(重力感应摇一摇等)
UIEventTypeRemoteControl, //远程事件
UIEventTypePresses API_AVAILABLE(ios(9.0)),//按压事件
};

iOS 中主要有以上几种事件,本文主要探讨触摸事件的产生、分发、响应过程。
整个iOS 触摸事件的产生到完成(销毁)的过程如下图:

事件的产生

  1. 当用户手指触摸屏幕时,屏幕硬件会将感应到的事件传递给IOKit,IOKit封装触摸事件为IOHIDEvent对象,通过 mach port(IPC进程间通信) 将事件发送给 SpringBoard.appSpringBoard.app 主线程runloop被IOKit.framework转发的消息唤醒,触发Source1回调__IOHIDEventSystemClientQueueCallback()
  2. SpringBoard.app 检测到有app在前台,通过mach port(IPC)转发给app。
  3. 前台app 主线程的Runloop被 Springboard转发来的消息唤醒,并触发Source1回调__IOHIDEventSystemClientQueueCallback(),该方法内部会触发Source0回调__UIApplicationHandleEventQueue(),Source0 回调内部封装IOHIDEventUIEvent, Source0回调内部调用UIApplicationsendEvent: 方法。

此时一个UIEvent事件产生了,开始进入事件传递流程。

事件传递机制

UIApplication 通过sendEvent:方法将事件传递给UIWindow,UIWindow递归调用UIView层级的hitTest:withEvent:方法,找到事件的第一响应者,并将该view返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if !self.userInteractionEnabled || self.hidden || self.alpha <= 0.01 {
return nil
}
if self.pointInside(point, withEvent: event) {
for view: UIView in self.subviews {
let covertPoint = view.convertPoint(point, fromView: self)
let hitTestView: UIView? = view.hitTest(covertPoint, withEvent: event)
if hitTestView != nil {
return hitTestView
}
}

return self
}

return nil
}

响应链

能响应事件的都是UIResponder及其子类,每个UIResponder都有nextResponder属性。UIWindow将事件分发给第一响应者后,如果第一响应者没有实现以下方法,那么就会将该事件交给nextResponder处理。如果沿着响应链没有发现能够响应事件的响应者,那么这个事件就会被忽略。
由此可见事件传递是由上向下传递,响应链是由下向上传递的。

1
2
3
4
public func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?)
public func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?)

UIGestureRecognizer & UIControl 响应事件过程

UIGestureRecognizer

在寻找第一响应者者的过程中,如果view绑定了UIGestureRecognizer,会将view的Recognizer添加进UITouchgestureRecognizers数组中,UIWindow会先将事件发送给Recognizer,成功识别事件的Recognizer所关联的UIView会收到touchesCancelled:消息并且以后改view不会再收到这个UIEvent消息。如果Recognizer没有响应,事件将会传递给第一响应者,并沿响应链向上传递。

UIControl

如果第一响应者是UIControl的子类,非系统级的UIControl类如UIButton,则优先响应UIGestureRecognizer,如果UIButton是第一响应者,则直接由UIApplication派发事件,调用sendEvent:forAction:

参考

深入浅出iOS事件机制
iOS触摸事件的流动
UIGestureRecognizer学习笔记
OS触控响应中那些没有细想过的问题
IOHIDFamily
iOS 事件处理机制与图像渲染过程