响应链

每一个应用有一个响应者链,我们的视图结构是一个N叉树(一个视图可以有多个子视图,一个子视图同一时刻只有一个父视图),而每一个继承UIResponder的对象都可以在这个N叉树中扮演一个节点。当叶节点成为最高响应者的时候,从这个叶节点开始往其父节点开始追朔出一条链,那么对于这一个叶节点来讲,这一条链就是当前的响应者链。响应者链将系统捕获到的UIEvent与UITouch从叶节点开始层层向下分发,期间可以选择停止分发,也可以选择继续向下分发。

例子:
我用SingleView模板创建了一个新的工程,它的主Window上只有一个UIViewController,其View之上有一个Button。这个项目中所有UIResponder的子类所构成的N叉树为这样的结构:


响应链_第1张图片

那么他看起来并不像N叉树,但是不代表者不是一颗N叉树,当我们项目复杂之后,这个View可不可以有多个UIButton节点?所以他就是一棵树。
实际上我们要把这棵树写完整,应该还要算上UIButton的UILabel和UIImageView,因为他们也是UIReponder的子类。这里先不考虑了。
我们对UIButton来讲,他此时若是叶节点,那么这时我们针对他所在的响应链来说,他在他之前的响应者就应该是我们controller的view(树中的叶节点比父节点永远更优先被分发事件,但是并不是说他就能在时间上先响应,我们下面讲为什么)。所以我们尝试在任意地方打印这个Button的nextReponder对象。nextResponder对象是UIReponder类的实例方法,它会返回任意对象在树中的上一个响应者实例:

控制台输出消息:**2013-09-21 03:40:25.989**  **响应链**  **[614:60b] >** ```
我们可以根据这个UIView的尺寸来得知,他就是我们唯一的控制器中的那个UIView。 
接下来我们再打印下这个UIView的下一个响应者是谁: 
```NSLog(@"%@",_testButton.nextResponder.nextResponder);
输出:  **2013-09-21 03:45:03.914**  **响应链**  **[621:60b] **  ```
依次看,接着加一个nextResponder: 
```**2013-09-21 03:50:49.428**  **响应链**  **[669:60b] (null)** ```
为什么这里ViewController没有父亲呢?注意这句代码我是写在ViewDidLoad中,而我们知道这个方法的生命周期比较早,所以我们换个地方写或者延迟一段时间再打印,两种方法都可以得到结果(由此可以推理出我们响应者树的构造过程是在ViewDidLoad周期中来完成的,这个函数会将当前实例的构成的响应者子树合并到我们整个根树中): 
```**2013-09-21 03:53:47.304**  **响应链**  **[681:60b] ; layer = >** ```
再继续往上追朔:double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"%@",_testButton.nextResponder.nextResponder.nextResponder.nextResponder); });
```**2013-09-21 03:56:22.043**  **响应链**  **[690:60b] ** ```
再加一个```:**2013-09-21 03:56:51.186**  **响应链**  **[696:60b] ** ```
那么我们的appDelegate还有没有父节点?```**2013-09-21 03:57:22.588**  **响应链**  **[706:60b] (null)** ```
没有了,注意,一个从叶节点开始分发的事件,最多也就只能分发到我们的AppDelegate了!  
这个树形结构在我们的项目中尤为重要,举个栗子,如果我们想在一个view中重写UITouchEvent的4个方法,并且不影响他的父视图也响应这些事件,就要注意你重写的方式了,比如我们在ViewController中重写touchBegan如下: 
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"ViewController接收到触摸事件");}
在appDelegate的中同样也写上这一段:-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ NSLog(@"appDelegate接收到触摸事件");}
那么究竟是谁被触发呢? 
```**2013-09-21 04:02:49.405**  **响应链**  **[743:60b] ```ViewController**  **接收到触摸事件**  
这个很好理解,我刚刚也说了,viewController明显是appDelegate的子节点,他有事件分发的优先权。如果我们想两个地方都触发呢?这里super一下就可以了:-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ [super touchesBegan:touches withEvent:event]; NSLog(@"ViewController接收到触摸事件");}
```输出:**2013-09-21 04:07:26.206**  **响应链**  **[749:60b] appDelegate**  **接收到触摸事件**  ```
**2013-09-21 04:07:26.208**  **响应链**  **[749:60b] ViewController**  **接收到触摸事件**  ```
注意看时间戳,appDelegate虽然优先级别不如ViewController,但是他响应的时间上面足足比ViewController早了0.002秒,我这里试了几次,都是相差0.002秒。那么我们分析一下这里的响应者链是怎样工作的: 
用户手指触摸到了UIView上,由于我们没有重写UIView的UITouchEvent,所以他里面和super执行的一样的,将该事件继续分发到UIViewController; 
UIViewController的TouchBegan被我们重写了,如果我们不super,那么我们在这里写响应代码。事件到这里就不继续分发了。可想而知,UIViewController祖先节点:UIWindow,UIApplication,AppDelegate都无权被分发此事件。 
如果我们super了TouchBegan,那么此次触摸事件由 
ViewController分发给UIWindow, 
UIWindow继而分发给UIApplication, 
UIApplication再分发给AppDelegate, 
于是我们在ViewController和appDelegate的touchBegan方法中都捕获到了这次事件。 
到这里大家应该对这个响应者树有一个很好的理解了吧? 
接下来我们再谈谈第一响应者,和UIButton上的事件分发。

你可能感兴趣的:(响应链)