系统注册了一个 Source 1(基于 mach port)用来接收系统事件,其回调函数为 _IOHIDEventSystemClientQueueCallback() 。当一个硬件事件(比如触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收,SpringBoard 只接收按键(锁屏/静音等)、触摸、加速、传感器等几种 event ,随后用 mach port 转发给需要的 APP 进程。随后苹果注册的哪个 Source 1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会以先进先出的顺序把 IOHIDEvent 处理并包装成 UIEvent 进行处理分发,其中包括识别 UIGesture /处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegan/Moved/End/Cancel 等都是在这个回调中完成的。
- 首先判断主窗口能否接收事件
- 触摸点是否在自己身上
- 若在,从后往前遍历自己的子控件,重复前两个步骤(能否接收事件?是否在控件上?)
- 直到找不到合适的子控件,那么自己就成为最合适的 view ,之后调用具体的如 touches 方法处理
底层实现主要涉及两个方法:func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? 和 func point(inside point: CGPoint, with event: UIEvent?) -> Bool
hitTest 方法会根据视图层级结构往上调用 pointInside 方法,确定能否接收事件。如果 pointInside 返回 true ,则继续调用子视图层级结构,直到在最远的视图找到点击的 point 。如果一个视图没有找到该 point ,则不会继续它往上的视图层级结构。
如果当前 view 是控制器的 view ,那么控制器就是上一个响应者,事件就传递给控制器;如果当前 view 不是控制器的 view ,那么父视图就是当前 view 的上一个响应者,事件就传递给它的父视图
- 如果 window 对象也不处理,则其将事件或消息传递给 UIApplication 对象
- 如果 UIApplication 也不能处理该事件或消息,则将其丢弃