事件传递与响应者链
wanyakun 12/14/2022
当我们触摸手机屏幕时,UIKit创建一个包含处理事件所需信息的事件对象(UIEvent),然后放到活动应用程序的事件对列里面,然后UIApplication单例从事件队列取出这个事件进行分发处理,通常事件发送到应用程序的key window对象,该对象将事件传递到初始对象(initial object,即responder),初始对象取决于事件的类型。对于触摸事件:接着需要找到响应这个事件的最佳视图,也就是responder。
# 事件传递
通过在显示视图层级中依次对视图调用这两个方法确认该视图是不是响应这个事件,首先会调用hitTest,然后hitTest会调用pointInside,最终hitTest返回的那个view就是最终的响应者responder
- 寻找事件的最佳响应视图是通过对视图调用hitTest和pointInsdie完成的
- histTest的调用顺序是从UIWindow开始,对视图的每一个子视图一次调用,子视图的调用顺序是从后往前,也可以说是从显示最上面到最下面
- 遍历直到找到响应视图,然后逐级返回最终UIWindow返回此视图
# hitTest:withEvent
返回视图层级中能够响应触控点的最深视图
# pointInside:withEvent
返回视图是否包含指定的某个点
# 响应者链
响应者链是一系列被链接起来的响应对象。它从第一响应者开始,到程序对象(UIApplication)结束,如果第一响应者不能处理事件,它转发事件到响应者链的下一个响应者。响应者对象是一个可以响应和处理事件的对象,继承自UIResponder
- 找到最适合的响应视图后,事件会从此视图开始沿着响应链nextResponder传递,直到找到处理事件的视图,如果没有处理的视图,事件会被丢弃
- 如果视图有父视图,则nextResponder指向父视图,如果是根视图则指向控制器,最终指向AppDelegate,他们都是通过重写nextResponder来实现。
# 无法响应情况
- alpha = 0
- 子视图超出父视图的情况
- userInteractionEnabled=NO
- hidden=Yes视图会被忽略,不会调用hitTest
注意:
- 父视图被忽略后其所有子视图也会被忽略
- 出现视图无法响应的情况,可以考虑上述情况来排查问题
# UIEvent是如何封装的?放到事件队列后又经历了什么?
当一个触摸事件发生后:
- 由IOKit.framework生成一个IOHIDEvent事件并由SpringBoard接收
- SpringBoard通过match port将事件转发给我们的App进程
- 触发App注册在RunLoop中的Source1来处理事件
- Source1触发__IOHIDEventSystemClientQueueCallback回调,回调后又会触发Source0
- 再后面就是UIApplication从事件队列取出事件派发