zoukankan      html  css  js  c++  java
  • 理解cocoa和cocoa touch的响应者链

    该文章翻译自:Understanding cocoa and cocoa touch responder chain
    转载注明出处:http://www.cnblogs.com/zhanggui/p/7157954.html

    不管是在cocoa中还是在cocoa touch中,所有的Applications都有一个与之关联的事件队列,这个队列里面是许多不同来源的事件。为了处理事件流,每个application都持有一个run loop,此run loop将会以先进先出(first in first out)的顺序接收和派发事件。
    当一个程序启动的时,对UIApplicationMain的调用将会创建一个UIApplication的单例对象,这个单例对象将会处理和调度系统发送到应用程序事件队列的事件。
    Application将会接收下面来源的事件:

    1. UIControl Actions:这些事使用action-target模式注册的动作,例如button添加的动作。
    2. User events:来自用户的事件,例如touches、shakes、motion等等。
    3. 系统事件:例如内存过低、旋转等。

    在被派发到适合的接收者之前,这些事件都会被上面提到的application单例对象处理一下。

    UIControl Actions

    UIControl Actions就是我们通过addTarget:action:forControlEvents:方法为control对象添加的action,UIControl对象将会保持并记录所有通过action/target添加的action。
    当用户在控件上执行事件的时候,或者当一个控件调用sendActionsForControlEvents方法的时候,和该控件相关的action事件将会被发送到注册的target。
    举个例子:

    UIButton *button = [UIButton new];
    [button addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventouchUpInside];
    

    当用户点击这个button的时候,事件将会被调度到UIAppication(使用UIcontrol内部的sendActionsForControlEvents副本),然后application会从事件队列里面读取并且在UIApplication的sendAction:to:from:forEvent:方法里面调度,该方法的基本实现就是将在注册的目标上调用动作,在这种情况下,目标将接收buttonTapped方法。

    如果我们把target置为nil:

    [button addTarget:nil action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
    

    此时sendAction:to:from:forEvent 将会将buttonTapped选择器发送到当前第一响应者,如果当前的第一响应者没有实现这个方法,那么它将被转发到下一个响应者,系统将会一直尝试在响应者链中去找一个可用的响应者,直到没有更多的响应者可用。在这种情况下,该操作将会被删除。
    根据上面所说的,我们可以利用UIapplication单例对象的sendAction:to:from:forEvent方法给第一响应者发送一个动作,将target置为 nil。
    例如我们可以发送resignFirstResponder消息给第一响应者来隐藏键盘:

    [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
    

    User Events

    用户事件,例如touch事件和设备运动事件,这些事件发送到application的事件队列里面,如果用户事件是touch事件之外的任何事情,application将会分发这个调用给第一响应者,如果第一响应者无法处理,系统会继续响应者链查找适当的响应者。
    对于touch事件,流程是不一样的。当系统检测到一个屏幕上的touch,它就会把这个touch发送给application,application会在其 _ touchesEvent内部方法中接收这个touch事件。
    然后application将会使用sendEvent将此事件转发到UIWindow,收到此事件后的Window会开始测试视图(hit-testing),以便找到接收此touch的视图。
    UIView将会使用hitTest:withEvent的方法来查找在这个touch事件之下的视图,hit-test会通过调用每个view的pointInside:withEvent:来检查该touch是否在当前view里面。
    hitTest和pointInside将被递归调用,直到它达到最顶层的叶视图。这个view将会被作为touch的第一响应者来处理这次touch。

    UIWindow 会将触摸事件发送到此视图。

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

    当一个事件发送到一个view的时候,这个view有下面三种选择:

    1. 由于上述四种方法的UIResponder base实现将事件转发给下一个响应者,那么如果视图没有实现他们的方法,则该方法将被转发到下一个响应者。
    2. 视图可以实现上述的任何一种方法,做一些处理,然后调用super,以让下一个响应者做一些额外的过程。
    3. 视图可以实现上述任何方法,并选择不将事件转发给下一个响应者。

    如果视图选择不处理这个touch事件,那么该事件将会发送到响应者链,然后按照下面的路径执行:

    1. 第一响应者是收到测试的视图(touch下的视图)
    2. 下一个响应者是它的父视图
    3. 该响应者链在视图层次结构上继续进行,直到达到与视图控制器相关的视图
    4. 这个视图控制器将会是下一个响应者
    5. 如果这个视图控制器是根视图控制器,那么window就是下一个响应者
    6. application是window的下一个响应者
    7. 在响应者链最后的响应者是App delegate

    System Events

    系统也会发送事件给application单例,application单例将会接收这些系统相关的事件,然后把他们派发到App delegate,app delegate将会依次接收和处理这些事件。

    The first responder

    任何的UIResponder对象都可以通过调用或者接收becomeFirstResponder方法来确定是否成为第一响应者,第一响应者将被接收到有机会对用户事件采取行动。然而,touch事件不会被发送到第一响应者,这些事件被发送到通过进行递归命中测试发现的视图。
    除了上面提到的,第一响应者也会接收到他们的target置为nil的UIControl动作。

  • 相关阅读:
    python接口自动化测试二十四:上传多个附件,参数化
    python接口自动化测试二十三:文件上传
    python接口自动化测试二十二:文件下载
    python接口自动化测试二十一:类和方法
    Delphi开发ocx插件的调试
    Confluence 6 推荐的更新通知设置和禁用
    Confluence 6 配置推荐更新邮件通知默认的初始化设置
    为 Confluence 6 分发包设置一个邮件会话
    Confluence 6 测试电子邮件设置
    为 Confluence 6 配置发送邮件消息
  • 原文地址:https://www.cnblogs.com/zhanggui/p/7157954.html
Copyright © 2011-2022 走看看