美文网首页IOS 手势点击
003UIKit-02-大话iOS Responder Chai

003UIKit-02-大话iOS Responder Chai

作者: 修_远 | 来源:发表于2021-07-23 18:41 被阅读0次

    网上绝大部分的博客在讲响应链的时候,都是对hitTest:withEvent:pointInside:withEvent:两个API的使用给了一些案例的解释。这两个API的作用、调用时机并没有给出很好的解释。很少有博客对event的来源做过讲解,绝大部分对“向上”和“向下”两个方向也是懵懵懂懂的。而这些原始的问题才是响应链比较关心的地方,响应链只是event handle其中的一部分。

    1. Event的来源;
    2. 向上和向下是怎么定义的;
    3. hitTest:withEvent:的作用以及递归的方向;
    4. pointInside:withEvent:的作用;

    一、Event结构

    1.1 Runloop

    1. 每个线程都有自己的runloop
    2. 主线程的runloop成为:main event loop
    3. main event loop在application初始化的时候创建
    4. runloop中有很多源,其中处理事件的源称为:event source的组成
      • port:用来接收window server传递过来的 event
      • FIFO queue:保存这些事件,直到application处理它们

    1.2 Event 来源和方向

    在Mac OS系统下,我们可以通过鼠标点击、键盘、手写板来输入事件,而在iOS系统中,最多的就是触摸屏幕来输入。从物理层来说,我们只能触摸屏幕,并不能“摸到”APP,所以第一层是I/O Kit接受外部输入,然后通过Core Services将消息传递给Window Server,这个时候就生成了Event事件。这个Window Server在Mac OS是非常常见的一层,众所周知,我们可以在电脑上同时开辟多个应用,这个消息要传给哪个应用,就需要通过Window Server层来决定。Window Server通过Mach PortEvent发送到APP中,具体位置就是上面说到的:main run loop中的event queue

    The event stream

    In OS X, events are delivered as an asynchronous stream. This event stream proceeds “upward” (in an architectural sense)
    through the various levels of the system—the hardware to the window server to the Event Manager—until each event reaches its final destination: an application. As it passes through each subsystem, an event may change structure but it still identifies a specific user action.

    苹果文档中指出:event stream事件流的方向是向上。在iOS应用开发中,表示从application到最上层子视图的方向。

    1.3 事件分发

    事件驱动-Event的分发。在main run loop的作用下,会不停地从event queue中读取event,具体过程如下:

    1. application 会从 event queue 中持续获取下一个event
    2. 然后将其转换成 NSEvent 对象
    3. 然后将 NSEvent 对象朝着它最终的方向分发
    4. 获取event通过调用方法:nextEventMatchingMask:untilDate:inMode:dequeue:
    5. 当没有event的时候,这个方法会停止。当有事件的时候恢复。
    The main event loop, with event source

    1.4 Action Message

    在Mac OS的系统中,会显示的区分Action Message的调用方式,这里主要介绍下与Event Message的区别。

    1. 所有从window server传递到application的event通过 sendEvent: 方法自动分发;
    2. action message通过sendAction:to:from:方法分发
      • selector:要调用的方法
      • target:谁接受这个方法,可能为空
        • target=nil:从整个响应链上查找实现了selector的对象
        • target不为空:直接给该target发送action message
      • object:产生action message的对象
    3. action message是用户自定义的消息,且不能提前指定;
    4. event message中NSPresponder提供了它们的所有方法的实现;
    5. action-message常见分发途径:NSResponder的tryToPerform:with:
      • 这个方法会检查receiver,看它有么有实现selector方法
        • 如果实现了,则调用这个方法
        • 如果没有实现,则传递给下一个响应者

    NSWindow和NSApplication重写了包括这个方法的所有代理方法与sendAction:to:from:不同的是,检测target响应selector的方法:respondsToSelector:tryToPerform:with:相似的方法是:doCommandBySelector:如果找不到实现它的响应者,硬件则会发出“嘟嘟嘟”的提示音。

    二、响应者和响应链

    响应者:直接或通过响应链接受event的对象
    响应链:响应链是一系列被应用event或action message的响应者对象的链

    2.1 first responders

    1. first responders一般是用户交互的对象(例如通过鼠标、键盘选择或启动的对象)
    2. 这些对象往往都是响应链上一个接收event或action message的对象
    3. NSWindow对象的第一响应者是它自己
    4. 当NSWindow接收到event时
      • 它会自动尝试让event下的NSView对象成为第一响应者
      • 通过acceptsFirstResponder方法来判断该对象是否是第一响应者
      • acceptsFirstResponder默认是NO,如果要成为第一响应者,则需要设置为YES
        可以通过makeFirstResponder:方法给NSWindow设置第一响应者

    上面关于first responders的介绍主要是针对MacOS中的API,对于iOS中会有一定的差异,下面通过iOS中的API进行分析。

    我们期待的是可以通过becomeFirstResponder方法将某个view设置为第一响应者,其中有以下几个注意点:

    Subclasses can override this method to update state or perform some action such as highlighting the selection.

    A responder object only becomes the first responder if the current responder can resign first-responder status (canResignFirstResponder) and the new responder can become first responder.

    You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy.

    1. 子类可以重写becomeFirstResponder方法更新状态;
    2. 仅仅当当前响应者注销第一响应者之后,下一个(新的)响应者才有机会变成第一响应者;
    3. 想要成为第一响应者,一定是要在UIWindow的视图层级中。

    也有一些博客介绍了重写<UIKeyInput>协议,然后重写这个属性才会生效,也许有效,但这并不是正确的姿势。从这个协议的名称不难猜出,这个事件是跟键盘的输入相关,而接受键盘输入的控件就是:UITextfield和UITextView,所以我们经常在这两个视图中会用到becomeFirstResponder方法。
    在后面的文章中会介绍如何正确的让一个view去响应事件。

    2.2 next responders

    关于一个对象的下一个响应者,往往遵循以下规则:

    1. view的next responders通常是它的superview(或者是viewController);
    2. addSubview: 会自动将view设置为subview的next responders;
    3. 在视图层中插入一个view之后,要确切的知道响应链顺序,这样才可以有效的按预期处理业务。
    The chain of next responders

    2.2 响应链

    1. 定义:响应链是一系列被应用event或action message的响应者对象的链;
    2. 行为:当给定的响应者对象不处理指定的消息,则会顺着链路传递给next responders
    3. 这种行为允许响应者将处理消息的责任委托为其他对象(通常是高级别的对象);
    4. 可以通过NSResponder的setNextResponder: 方法在响应链中手动插入一个响应者,而且可以通过nextResponder方法查找到这个响应者
    5. application可以有非常多的responder chain,但是同一时间,只有一条responder chain是活跃的;
    6. responder chain for event message
      默认响应链是event message从Window将消息传递到视图开始;
          这个视图被默认为第一响应者
          这个视图一般是在视图的最上层
      如果first responders没有处理event message,则会传递给next responders,直到消息被处理
      如果一直没有处理消息的对象,在响应链的最后一个响应者就会调用`noResponderFor:`,并发出“哔哔”声
      
    7. responder chain for action message

    在MacOS中,定义了action messages三种类型的响应链结构,如下所示:

    1. 非文档模型的应用(iOS中的应用也是这种模型);
    2. 存在NSWindowController对象的非文档模型的应用;
    3. 文档模型的应用;
    Responder chain of a non-document-based application for action messages Responder chain of a non-document application with an NSWindowController object (action messages) Responder chain of a document-based application for action messages

    到这里,我们对整个事件处理中涉及到的一些概念都有了比较清晰的认识了。但在iOS应用中,文章开始提出的另外两个问题并没有讲解清楚。请看下回分解……

    1. hitTest:withEvent:的作用以及递归的方向;
    2. pointInside:withEvent:的作用;

    参考资料:
    Cocoa Event Handling Guide
    Using Responders and the Responder Chain to Handle Events
    hitTest:withEvent:
    pointInside:withEvent:

    相关文章

      网友评论

        本文标题:003UIKit-02-大话iOS Responder Chai

        本文链接:https://www.haomeiwen.com/subject/lzreultx.html