iOS响应机制简述

392 阅读2分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战」。

前言

iOS 响应链是 APP 交互的响应基础,只有继承自UIResponder的子类,才可以触发响应机制。

UIResponder 是 iOS 中用于处理用户事件的 API,可以处理触摸事件,主要是使用下面的方法

// 触摸开始
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸移动
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸取消(在触摸结束之前)
// 某个系统事件(例如电话呼入)会打断触摸过程
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
// 触摸结束
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

对于触摸事件,系统提供了上面四种方法来处理。如果重写了上述方法,那么事件就会在此中断,并且不再沿着事件响应链进行传递; 如果需要继续进行传递,则需要调用 super 方法。

响应机制主要有 传递链响应链两部分构成

事件传递机制

传递链:事件传递是从 UIApplication 管理起点, 逐级向下传递。

事件的传递是自上而下触发的 UIApplication -> UIWindow -> root view -> ··· -> subview -> first view

image.png

  1. 当 iOS 程序中发生触摸事件后,系统会将事件加入到 UIApplication 管理的一个任务队列中
  2. UIApplication 将处于任务队列最前端的事件向下分发。即 UIWindow。
  3. UIWindow 将事件向下分发,即 UIView。
  4. UIView 首先看自己是否能处理事件,触摸点是否在自己身上。如果能,那么继续寻找子视图。
  5. 遍历子控件,重复3,4两步。如果没有在子控件找到触摸点,那么自己就是事件处理者。
  6. 如果自己不能处理触摸响应,那么不做任何处理。

事件分发有几种情况是不作处理的

  1. alpha < 0.01
  2. userInteractionEnabled = NO
  3. hidden = YES

若父视图事件不做处理,(以上三种情况),那么子视图也不会传递事件,改事件传递被废弃。

事件传递的两个核心方法:

//此方法返回的view是本次点击事件需要的最佳view,用来找到最终响应的view
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event

//判断一个点是否在视图范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

1.通过重写hitTest方法,可将点击的点移动到子view。
2.重写pointInside方法,可扩大按钮的点击区域。

eg: 按钮的点击区域重写,小于44*44做处理,大于不处理
下面示例是定义一个button的可点击区域
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect bounds = self.bounds;
    CGFloat widthDelta = MAX(44 - bounds.size.width, 0);
    CGFloat heightDelta = MAX(44 - bounds.size.height, 0);
    bounds = CGRectInset(bounds, -0.5 * widthDelta, -0.5 * heightDelta);
    return CGRectContainsPoint(bounds, point);
}

hitTest:withEvent: 方法的处理流程如下:

  1. 先通过 pointInside:withEvent: 方法判断当前触摸点是否在当前视图内,如果返回 NO,则直接终止。hitTest:withEvent: 返回 nil。
  2. 若返回 YES,既可以继续往下传递,向当前视图的所有子视图发送hitTest:withEvent: 消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕。
  3. 若有子视图返回非空对象,则 返回此对象,处理结束。(子视图判断过程也是通过 pointInside:withEvent: 判断的)。
  4. 如果所有 subview 遍历结束仍然没有返回非空对象,则 hitTest:withEvent: 返回自身 (self)。

事件响应机制

响应链: 响应链则从下向上传递。响应链是通过 nextResponder 属性组成的一个链表,点击的 view 有 superView, nextResponder 就是 superView;

响应机制自下而上 first view -> super view -> ··· -> view controller -> window -> Application -> AppDelegate

image.png

  1. 通过事件传递找到的 first responder。
  2. 若 view 的 vc 存在,则将该事件传递给其vc响应;如若不存在,则传递给其父视图。
  3. 若 view 的最顶层不能处理事件,则传递给UIWindow进行处理。
  4. 若 UIWindow 不能处理,则传递给 UIApplication。
  5. 若 UIApplication 不能处理,则将该事件丢弃。