结合 RunLoop 和实际堆栈信息解释点击事件的传播(与 99% 的人认为的过程不同)。最终结果在最后的堆栈信息图和手绘的
中。
- 事件完整传递图
像我这种小白开发一般都是从事件的传递来讲的:就是 UIApplication 找寻最优响应者的过程(这里就不赘述了)。
好吧,直接给出总结的答案:
简短描述: IOKit 负责响应硬件事件,Darwin 内核发出 Source1 <mach_port> 消息。
网上大多数的 RunLoop 基本上都是抄自这个。确实讲的很好,我也读过这个 blog,但是感觉根据 blog 里对点击事件的讲解理解还是有点抽象。
ibreime 的原文中对 Source0 和 Source1 的描述如下:
1,Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
2,Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
- - (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),
当一个硬件事件 (触摸 / 锁屏 / 摇晃等) 发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。这个过程的详细情况可以参考这里。SpringBoard 只接收按键(锁屏 / 静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的 App 进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。 _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture / 处理屏幕旋转 / 发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
至此一次 Button 的点击事件结束,虽然 RunLoop 天天说,但是在实际开发中却不常用,但是,其实 RunLoop 就跟设计模式一样,无处不在,知识串起来之后其实对很多 Bug 的 fix 和优化会有很大启发。
End.
来源: https://juejin.im/post/5a3a5ee2f265da431d3cd547