结合RunLoop
和实际堆栈信息解释点击事件的传播(与99%的人认为的过程不一样)。最终结果在最后的堆栈信息图
和手绘的事件完整传递图
中。设计模式
像我这种小白开发通常都是从事件的传递来说的:就是UIApplication找寻最优响应者的过程(这里就不赘述了)。bash
好吧,直接给出总结的答案:架构
简短描述: IOKit负责响应硬件事件,Darwin内核发出Source1 <mach_port> 消息。oop
网上大多数的RunLoop基本上都是抄自这个。确实讲的很好,我也读过这个blog,可是感受根据blog里对点击事件的讲解理解仍是有点抽象。优化
ibreime的原文中对Source0和Source1的描述以下:spa
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?3d
因而,我决定画个图配合堆栈信息讲述下点击事件的全过程,也帮助各位联系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.