• iOS事件响应链


     

    事件传递之响应链


    当你设计App时你可能需要动态的响应事件。例如,一个触摸事件可能发生在屏幕上不同的对象中,你需要决定哪个对象来响应这个给定的事件,理解对象如何接收事件。

    当用户触发的一个事件发生,UIKit会创建一个包含要处理的事件信息的事件对象。然后她会将事件对象放入active app’s(应用程序对象,每个程序对应唯一一个)事件队列。对于触摸事件,事件对象就是UIevent对象封装的一系列触摸集合。对于动作事件,这个事件对象依赖于使用的framework和你关心哪种动作事件。

    事件通过特殊的路径传递直到被传递到一个可以处理该事件的对象。首先,单例的UIApplication对象从顶层的队列中获取事件,然后分发。典型的,它将事件发送到App的关键window(key window)对象,window则为了处理该事件而发送它到初始化对象(initial object),这个初始化对像依靠事件类型。

    事件传递路径的最终目的时找出能处理和响应该事件的对象。因此,UIKit给适合处理该事件的对象发送事件。对于触摸事件,这个对象就是hit-test view,对于其他事件,这个对象就是第一个响应器(first responder)。下面的章节解释了hit-test view和first responder对象是如何被确定的。

    Hit-Testing返回触摸发生的view

    iOS使用hit-testing寻找触摸的view。 Hit-Testing通过检查触摸点是否在关联的view边界内,如果在,则递归地(recursively)检查该view的所有子view。在层级上处于lowest(我理解就是离用户最近的view)且边界范围包含触摸点的view成为hit-test view。确定hit-test view后,它传递触摸事件给该view。

    举例说明,假设用户触摸了图中的view E。iOS通过如下顺序查找hit-test view。

    1. 触摸点在view A中,所以要检查子view B和C。
    2. 触摸点不在view B中,但在C中,所以检查C的子view D和E。
    3. 触摸点不在D中,但在E中。

    View E是这个层级上处于lowest的view的边界范围包含触摸点,所以它成为了hit-test view。

    hitTest:withEvent:方法通过传递进来CGPoint和UIEvent返回hit test view。该方法调用pointInside:withEvent:方法,如果传入hitTest:withEvent:的point在view的边界范围内,则pointInside:withEvent:返回YES。然后,这个方法会在view的所有子view中递归的调用hitTest:withEvent:。

    如果传入hitTest:withEvent:的point在view的边界范围内,则pointInside:withEvent:返回NO。这个point会被忽略,hitTest:withEvent:返回nil。如果一个子view返回NO,则它所在的view的层级上的分支的子view都会被忽略。

    Hit-test view是处理触摸事件的第一选择,如果hit-test view不能处理事件,该事件将从事件响应链中寻找响应器,直到系统找到一个处理事件的对象。具体见“The Responder Chain Is Made Up of Responder Objects”

    响应器链由响应器对象组成(The Responder Chain Is Made Up of Responder Objects)

    一些类型的事件的传递依赖响应器链。响应器链(responder chain)是一系列相关的响应器对象。它开始于第一个响应器终止于应用对象(application object)。如果第一个responder不处理事件,则会根据responder chain将event传递给下一个responder。

    Responder object,即可以响应和处理事件的对象。UIResponder类是所有responder对象的基类,它定义了动态的接口,不仅处理事件也包括处理响应行为。包括UIApplication,UIViewController,和UIView类都是responder,这意味着所有view和大部分关键的controller对象都是responder。足以Core Animation layers不是responders。

    First responder被设计来第一个接收事件。典型的,first responder是一个view object。之所以成为第一个responder由于两个原因:

    1. 覆盖canBecomeFirstResponder方法,返回YES。
    2. 接收becomeFirstResponder消息。如果必须,一个object能发送给自身这个消息。

    。。。

    响应器链遵照一个特殊的传递路径(The Responder Chain Follows a Specific Delivery Path)

    如果初始化对象(initial object)—— 即hit-test view或者first responder —— 不处理事件,UIKit会将事件传递给responder chain的下一个responder。每个responder决定它是传递事件还是通过nextResponder方法传递给它的下一个responder。这个操作继续直到一个responder处理event或者没有responder了。

    Responder chain 序列在iOS确定一个事件并将它传递给initial object(通常是view)时开始。所以initial view有处理事件的第一个机会。下图描述了两个不同的事件传递路径(因为不同的app 设置)。一个App的事件传递路径由app特殊的构成决定,但事件传递路径会遵守相同的规则。

    关键方法

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 返回在层级上离当前view最远(离用户最近)且包含指定的point的view。

    关于hitTest方法的解释见hitTest:withEvent:方法流程

    - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 返回boolean值指出receiver是否包含指定的point。

    如下调用:手动指定当前view不响应事件

    1
                2
                3
                4
                5
                6
                7
                
    -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {    for (UIView *view in self.subviews) {        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
                            return YES;
                    }
                    return NO;
                }

    总结:

    事件的传递和响应分两个链:

    • 传递链:由系统向离用户最近的view传递。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
    • 响应链:由离用户最近的view向系统传递。initial view –> super view –> …..–> view controller –> window –> Application

  • 相关阅读:
    Android学习进程 Java引用 Rxjava MVP
    小试 Xcode 逆向:App 内存监控原理初探
    春招路上孤独的iOSer的心路历程(面经)
    【译】4个你需要知道的Asset Catalog的秘密
    超全!iOS 面试题汇总
    整理 iOS 9 适配中出现的坑(图文)
    旧版Xcode下载地址
    xcode 自动添加注释,生成文档
    NDK_ROOT找不到的解决方法 MACOS
    13个小技巧帮你征服Xcode
  • 原文地址:https://www.cnblogs.com/chengfang/p/4104095.html
Copyright © 2020-2023  润新知