iOS 响应者链

概述


iOS 响应者链(Responder Chain) 是支持App界面交互的重要基础, 点击, 滑动, 旋转, 摇晃等都离不开其背后的响应者链.

响应者 (Responder)


当我们触控手机屏幕时, 系统便会将这一操作封装成一个UIEvent 放到事件队列中, 然后Application 从事件队列中取出这个事件, 按着需要找到去响应这个事件的最佳视图就是 Responder.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {}

首先从UIWindow开始调用hitTest, 然后来到RootView的hitTest & pointInside. 继续遍地rootView子视图(从后添加的view开始), 目标视图的superView hitTest 返回 superView, pointInside 返回 YES; 同级非目标视图hitTest 返回 nil, pointInside 返回 NO; 目标视图hitTest 返回 目标视图, pointInside 返回 YES, 最终找到响应视图!

  • 结论
  1. 寻找事件的最佳响应者是通过 hitTest & pointInside 完成的;
  2. hitTest 调用顺序是从UIWindow开始, 对视图每个子视图依次调用, 子视图的调用顺序是从后往前, 即后添加的先调用
    3.遍历子视图知道找到响应视图, 然后逐级返回最终到UIWindow返回此视图.

处理者


在上面我们找到了点击事件的响应者, 但是我们未添加响应的点击处理逻辑(tapGestureRecognizer), 所以目标视图不会处理.
能够处理UI事件都是继承UIResponder的子类对象, UIResponder 主要有以下四个方法来处理事件:

- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(nullable UIEvent *)event;

如果某个Responder 没有处理事件, 事件会被传递. UIResponder 对象都有一个nextResponder 属性, 用于返回响应者链(Responder chain)中的下一个事件处理者, 如果所有Responder都不处理事件, 那么事件将会被丢弃.

// 打印响应者链
 UIResponder *nextResponder = self.view3.nextResponder;
    NSMutableString *pre = [NSMutableString stringWithString:@"--"];
    NSLog(@"View3");
    while (nextResponder) {
        NSLog(@"%@%@", pre, NSStringFromClass([nextResponder class]));
        [pre appendString:@"--"];
        nextResponder = nextResponder.nextResponder;
    }
image.png

下一个响应者规则:
如果有父视图则nextResponder为父视图, 如果是控制器根视图则指向控制器, 如果控制器在导航控制器中则指向导航控制器, 如果是根控制器则指向UIWindow, UIWindow的nextResponder指向UIApplication, 最后指向APPdelegate, 它们实现这一套是通过重写nextResponder实现的.

  • 结论
    1.找到最合适的响应视图后, 事件会从此视图开始沿着响应者链nextResponder传递, 直到处理事件的视图, 如果没有响应者处理事件则丢弃.
    2.如果视图有父视图则nextResponder指向父视图, 如果是根视图则指向控制器, 最终指向APPdelegate, 他们都是通过重写nextResponder 来实现的.

无法响应的情况


不会调用hitTest的情况

  • alpha == 0,
  • 子视图超出父视图,
  • userInteractionEnabled == NO
  • hidden == YES

Runloop 处理点击事件

Runloop 是app 运行的基础机制, 它一直处于接收消息-> 等待-> 处理的循环中, 当没有事件处理时会处于休眠状态, 等待下一个事件到来的唤醒去处理事件.
当一个触摸事件发生后, 首先是由IOKit.framwork 生成一个IOHIDEvent事件并由Springboard接收, 然后springboard会通过mach port 将事件转发给 app 进程, 然后触发app注册在runloop中的source1来处理事件, source1 会触发 __IOHIDEventSystemClientQueueCallBack回调, 回调后又触发 source0, 在后面就是 UIApplication从事件队列中取出事件分发.

你可能感兴趣的:(iOS 响应者链)