iOS 底层 day30 iOS触摸事件

一、本节只记录问题,答案和细节从大神的文章中寻找

  • 大神文章链接 https://www.jianshu.com/p/c294d1bd963d

二、描述触摸事件的生命周期(从用户触碰屏幕开始)

1. 系统响应阶段

1.1 手指触碰屏幕,屏幕感受到触碰后,将事件交由 IOKit 处理。
1.2 IOKit 将触摸事件封装成IOHIDEvent 对象,并通过 mach port 传递给 SpringBoard 进程。

mach port 是 macOS 的 IPC 进程通信方式,各个进程之间通过它通信。
SpringBoard.app 是一个系统进程,可以理解为桌面系统,可以统一管理和分发系统收到的触摸事件。

1.3 SpringBoard 进程因接收到触摸事件,触发了主线程的 RunLoopsource1 事件源回调,会把事件传递给当前屏幕运行的 APP。(暂不讨论当前屏幕进程就是 Springboard 的情况)

2. APP响应阶段

2.1 Springboard 进程将事件通过 mach port 传递给当前 APP,会唤醒当前 APP 的 RunLoop 并且触发 source1 的回调。
2.2 source1 的回调又会触发 source0 的回调,source0 将事件封装成 UIEvent 对象,并且将触摸事件添加到 UIApplication 对象的事件队列中。
2.3 事件出队列后,开始了寻找最佳响应者的过程,这个过程又称为 hitTest 过程。
2.4 寻找到最佳响应者后,接下来 UIApplication 会调用 sendEvent: 将事件分发给最佳响应者
2.5 最佳响应者拿到事件后,可以决定对事件进行独自消化,也可以选择让事件在响应者链条中继续传递
2.6 触摸事件历经坎坷后要么被某个响应对象捕获后释放,要么致死也没能找到能够相应的对象,最终释放。至此,这个触摸事件的使命就算终结了,RunLoop 若没有其他事件需要处理,也将重归于眠,等待新的事件到来后唤醒。

三、UITouch 、 UIEvent、UIResponder 分别是什么?

1. UITouch

触摸的起源

  • 一个手指一次触摸屏幕,就对应生成一个 UITouch 对象。多个手指同时触摸,生成多个 UITouch 对象。
  • 多个手指先后触摸,系统会根据触摸的位置判断是否更新同一个 UITouch 对象。
  • 每个 UITouch 对象记录了触摸的一些信息,包括触摸时间、位置、阶段、所处的视图、窗口等信息。
  • 手指离开屏幕一段时间后,确定该 UITouch 对象不会再被更新将被释放。
2. UIEvent

事件的真身

  • 触摸的目的是生成触摸事件响应者响应,一个触摸事件对于一个 UIEvent 对象,其中 type 属性标识了事件的类型。
  • UIEvent 对象中包含了触发该事件的触摸对象的集合,因为一个触摸事件可能是由多个手指同时触摸产生的。触摸对象集合通过 allTouches 属性获取。
3. UIResponder

一切都是为了满足它的野心

  • 每个响应者都是一个 UIResponder 对象,即所有派生自 UIResponder 的对象,本身都具备响应事件的能力。因此以下类的实例都是响应者:UIViewUIViewControllerUIWindowAppDelegate

  • 响应者之所以能响应事件,因为其提供了 4 个处理触摸事件的方法:

//手指触碰屏幕,触摸开始
- (void)touchesBegan:(NSSet *)touches withEvent:(nullable UIEvent *)event;
//手指在屏幕上移动
- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;
//手指离开屏幕,触摸结束
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;
//触摸结束前,某个系统事件中断了触摸,例如电话呼入
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;

四、寻找事件的最佳响应者(hit-tested View)

1. 如何寻找最佳响应者?
  • 自上而下:(父视图 → 子视图)
  • 从后往前 (同级视图,优先询问后添加的视图)
  • 若视图没有能响应的子视图了,则自身就是最合适的响应者。
2. 如何判断视图能否响应?

通过 hitTest:withEvent: 方法的返回值来判断:

  • 若当前视图无法响应事件,则返回 nil
  • 若当前视图可以响应事件,当无子视图可以响应事件,则返回自身作为当前视图层中的事件响应者
  • 若当前视图可以响应事件,同时有子视图可以响应,继续调用子视图的 hitTest:withEvent: ,直到找到 最佳响应者
3. hitTest:withEvent: 的内部实现?
  • 先判断视图 userInteractionEnabled hidden alpha 三个属性是否符合
  • 再判断点击事件的触发位置是否在视图之内
  • 再从后往前调用子视图的 hitTest:withEvent: 方法
  • 最后返回找到的最佳响应者

五、事件的响应以及在响应链中的传递

1. 找到最佳响应者之后,事件是如何传递给最佳响应者的?
  • 首先,UIApplication 将事件通过 sendEvent: 传递给事件所属的 window
  • 然后,window 同样通过 sendEvent: 再将事件传递给 最佳响应者
2. 什么是响应链?

最佳响应者首先接收到事件,然后便拥有了对事件的绝对控制权。它可以选择独吞这个事件,也可以将这个事件往下传递给其他响应者,这个由响应者构成的视图链就称之为响应链

六、总结

  • 触摸发生时,系统内核生成触摸事件,先由 IOKit 处理封装成 IOHIDEvent 对象,通过 IPC 传递给系统进程 SpringBoard,而后再传递给前台 APP 处理。
  • 事件传递到 APP 内部时被封装成开发者可见的 UIEvent 对象,经过 hit-test 寻找第一响应者,而后由 Window 对象将事件传递给 hit-tested view,并开始在响应链上的传递。
  • UIResponder、UIGestureRecognizer、UIControl,笼统地讲,事件响应优先级依次递增。

七、补充

1. 如何通过给定的 view,拿到它的控制器 Controller ?
- (UIViewController *)viewController
{
    //获取当前view的superView对应的控制器
    UIResponder *next = [self nextResponder];
    do {
        if ([next isKindOfClass:[UIViewController class]]) {
            return (UIViewController *)next;
        }
        next = [next nextResponder];
    } while (next != nil);
    return nil;
}

你可能感兴趣的:(iOS 底层 day30 iOS触摸事件)