大概流程
从iphone
屏幕点击,到事件响应大概经历了这几个过程
苹果注册了一个Source1
(基于 mach port
的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework
生成一个 IOHIDEvent
事件并由 SpringBoard
接收。SpringBoard
只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种Event
,随后用 mach port
转发给需要的App进程。随后苹果注册的那个Source1
就会触发回调,并调用 _UIApplicationHandleEventQueue()
进行应用内部的分发。
_UIApplicationHandleEventQueue()
会把 IOHIDEvent 处理并包装成 UIEvent
进行处理或分发,其中包括识别UIGesture/处理屏幕旋转/发送给 UIWindow
等。通常事件比如 UIButton 点击
、touchesBegin/Move/End/Cancel
事件都是在这个回调中完成的。
- 接收到屏幕点击事件后,会包装成
Source0
类型的Event事件添加到Main Runloop
中 - 主线程执行
Source0
,并开始事件传递
,确认响应者,即由UIApplication传到UIWindow,再由父视图传子视图
,即由下至上
传递,通过pointInside:withEvent:
判断是否在点击范围(在范围内则返回YES,反之
)和hitTest:withEvent:
方法获得最终处理该事件的View(返回值为UIView
) - 确认响应者之后,开始
响应链
,触发touchesBegan
等触摸事件,通过由nextResponder
属性传递,一级一级往下传递,一般来说nextResponder
会设置成父视图,所以一般都是由上至下
注意事项:
- 点击事件类型为
Source0
,由Runloop
源码中得知,详细为__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- 响应者指的是继承
UIResponser
的对象,包括最常见的UIView
和UIViewController
甚至是UIApplication
,所以我们的UIView和UIViewController都是作为响应事件的载体 事件传递
的线路和响应链
的线路可能不一致pointInside:withEvent:
仅仅判断是否在点击范围内,真正的判断是谁处理Event是由hitTest:withEvent:
返回的UIView
Source0
根据Runloop源码,处理的事件有下列6种
// main dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
通过触摸事件中断点我们得知
#pragma mark - 触摸事件
//__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"来了,老弟!!!");//断点5
}
断点5的堆栈信息如下:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000102354b14 RunloopSourceCode`-[ViewController touchesBegan:withEvent:](self=0x0000000141d117e0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00000002819546c0) at ViewController.m:52
frame #1