正文
问题Fix:
结合
RunLoop
和实际堆栈信息解释点击事件的传播(与99%的人认为的过程不同)。最终结果在最后的
堆栈信息图
和手绘的
事件完整传递图
中。
事情经过:被某大佬问了个问题:描述下Button的点击事件
像我这种小白开发一般都是从事件的传递来讲的:就是UIApplication找寻最优响应者的过程(这里就不赘述了)。
好吧,直接给出总结的答案:
首先,需要具备RunLoop知识,如果RunLoop快忘光了不建议直接阅读请点击:
YY作者ibireme的博客
前导图【Darwin内核架构图,引自ibireme博客】:
简短描述:
IOKit负责响应硬件事件,Darwin内核发出Source1 <mach_port> 消息。
网上大多数的RunLoop基本上都是抄自这个。确实讲的很好,我也读过这个blog,但是感觉根据blog里对点击事件的讲解理解还是有点抽象。
如果对RunLoop比较了解,Continue:
ibreime的原文中对Source0和Source1的描述如下:
1,Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
2,Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
下面结合一个最简单的点击事件分析下:
事件描述:点击屏幕,在
- touchesBegan
处打断点
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
预想过程:
Source1和Source0都可以唤醒RunLoop,所以应该是RunLoop收到Source1直接封装成UIEvent再分发,但是实际发现,RunLoop的堆栈调用信息中并没有Source1的身影,只有Source0?
于是,我决定画个图配合堆栈信息讲述下点击事件的全过程,也帮助各位联系RunLoop的知识。
解释:
堆栈调用信息:
按照RunLoop的说法,这里应该是Source1唤醒RunLoop才对,但是堆栈信息中却没有收到Source1信息,只有Source0(UIEvent属于Source0),
结合ibireme博客对事件响应的描述:
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。