深入理解touch事件和响应者链

原文地址:http://nsomar.com/understanding-cocoa-and-cocoa-touch-responder-chain/

每一个cocoa应用都对应有一个事件队列来保存发生的事件,这个事件队列接受多种类型的事件源发出的事件。为了能够流畅的处理事件,每一个应用都在内部维护一个runloop来接受和转发事件。

当我们应用启动的时候会调用UIApplicationMain方法,这个方法将创建一个UIApplication的单例对象,这个对象将负责处理和转发事件队列中的事件。

应用的事件队列中一般存在如下三种事件类型:

  • UIControl Aaction:如设定在按钮上的目标和处理方法对。
  • 用户事件: 触摸,摇晃等事件。
  • 系统事件: 内存,电量等系统事件.

每一个这样的事件在转发给目标处理对象之前都是需要application单例来管理的。

UIControl Actions:

UIControl actions是通过addTarget:action:forControlEvents:方法加载到UIControl控件上的事件,UIControl将会保存action/target对。

当用户出发了这个UIControl对象或者这个UIControl调用了sendActionsForControlEvents函数,那么将会去target指定的对象处调用action指定的方法(切记不是直接去调用,还是要先转发给application 单例管理的事件队列)

像这个例子:

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

当用户点击了按钮,事件将发送给UIApplication(其实点击后UIControl内部就会调用sendActionsForControlEvents产生一个事件)存入队列,application读取队列中的action并使用UIApplication sendAction:to:from:forEvent:函数,这个函数的最基本功能就是找到注册的target,然后想起发送注册的action事件。

如果我们将target指定为nil的时:

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

在这个场景下,sendAction:to:from:forEvent将会去寻找此时在响应者链中的第一响应者去处理。

如果第一响应者无法处理这个action(既本例中的buttonTapped方法),那么他将发送给响应者链的下一个响应者,系统将一直在响应者链中去寻找能够处理action的对象知道链表最后,那么这个action将会被抛弃。

使用这个特性我们可以很方便的去使用UIApplication的单例对象的 sendAction:to:from:forEvent方法然后将target设定为nil去给第一响应者发送ation了。

例如我们可以让第一响应者辞去第一响应者角色从而关闭键盘等:

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

 

 

用户事件

如触摸和摇动设备等产生的用户事件都将发送到application的事件队列中,如果这个事件不是touch事件那么application会将此事件转发给第一响应者去处理,如果第一响应者无法处理,系统将沿着响应者链去寻找合适的事件处理对象。

对于touch事件来说处理流程上有点不一样,当系统感知到屏幕被touch了,他将这个touch事件发送给application,application将通过_touchesEvent内部方法收到touch事件

接着application会将使用sendEvent方法将这个事件转发给UIWindow对象,UIWindow对象就会发起hit-testing view流程去找寻处理该事件的view对象。

hit-testing的过程为是是使用hitTest:withEvent和pointInside:withEvent:不断饿递归调用来找到被touch的view。

UIWindow 将会给这个被知道的view(即hit-test view)发送以下四种方法:

- (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(hit-test view)会有如下几种处理选择:

  • 如果当前View没有实现其中某个方法,那个这个方法将会在nextponder对象中去尝试调用。
  • A view can implement any method from the above, do some processing, and then call super in order to let the next responder do some additional process.
  • 这个类也可以实现上述所有方法,让后做出一些处理,接着直接调用父类的方法从而去让nextResponder 做额外的处理。
  • 当然这个view可以实现所有的这些方法,全部自己处理完毕。不让next reponder参与进来。

当这个view不能处理touch事件的时候,那么他将此事件发送给响应者链,这个响应者链的路径是这样的:

  • The first responder is the hit-tested view (the view under the touch)
  • 第一响应者为hit-tested view(既被触摸的view)
  • 下一个响应者为他的父view
  • The chain continues up the view hierarchy until it reaches a view that is associated with a view controller
  • 知道达到包含这些view的controller
  • That view controller will be the next responder
  • controller就作为下一个响应者了。
  • If this view controller is a root controller, then the window will be the next responder
  • 如果此时view controller是根controller,那么window对象将是下一响应者
  • The application is the window's next responder
  • application将是window对象的下一响应者
  • The last responder in the chain is the App delegate
  • 最后的响应者为app的代理

 

responder chain

 

 

系统事件:

系统将会发送一些事件给Application的单例对象,这些和系统相关的事件被application单例接受到后将转送给app的代理。app 代理将会以此地去结束和处理他们。

第一响应者

UIResponder可以通过becomeFirstResponder方法来注册成为第一响应者,第一响应者将会第一个去处理用户事件,当然touch事件如前所述将不会让第一响应者去处理,他将通过hit-testing过程去找寻hit-tested view去处理。

另外,第一响应者也有责任去第一个处理那些没有指定target的UIControl action。

 

posted on 2016-02-04 10:32  xf-xrh-xf  阅读(1010)  评论(0)    收藏  举报

导航