关于响应者链,有如下一段介绍:每一个应用有一个响应者链,我们的视图结构是一个N叉树(一个视图可以有多个子视图,一个子视图同一时刻只有一个父视图),而每一个继承UIResponder的对象都可以在这个N叉树中扮演一个节点。当叶节点成为最高响应者的时候,从这个叶节点开始往其父节点开始追朔出一条链,那么对于这一个叶节点来讲,这一条链就是当前的响应者链。响应者链将系统捕获到的UIEvent与UITouch从叶节点开始层层向下分发,期间可以选择停止分发,也可以选择继续向下分发。
那,我要是告诉你们,响应者链就是上面那段话介绍的,估计你们得拿板砖拍我了。这等于没说。别急,先来举个栗子:
我用SingleView模板创建了一个新的工程,它的主Window上只有一个UIViewController,其View之上有一个Button。这个项目中所有UIResponder的子类所构成的N叉树为这样的结构:
那么他看起来并不像N叉树,但是不代表者不是一颗N叉树,当我们项目复杂之后,这个View可不可以有多个UIButton节点?所以他就是一棵树。
实际上我们要把这棵树写完整,应该还要算上UIButton的UILabel和UIImageView,因为他们也是UIReponder的子类。这里先不考虑了。
所以我们尝试在任意地方打印这个Button的nextReponder对象。nextResponder对象是UIReponder类的实例方法,它会返回任意对象在树中的上一个响应者实例:
控制台输出信息如下:
<UIView: 0x7f8973e92b60; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7f8973e92ee0>>
咱们可以一直打印下去,获取下一个Responder的下一个Responder,依次获取的响应者是:
<ViewController: 0x7f8973c2ccf0>
(null)
为什么这里ViewController没有父亲呢?
注意这句代码我是写在ViewDidLoad中,而我们知道这个方法的生命周期比较早,所以我们换个地方写或者延迟一段时间再打印,两种方法都可以得到结果(由此可以推理出我们响应者树的构造过程是在ViewDidLoad周期中来完成的,这个函数会将当前实例的构成的响应者子树合并到我们整个根树中):
最后获取就是上边给出的那幅图的样子了。那说了这么多,这个Responder到底有什么用呢?
在AppDelegate里面重写touchesBegan方法:
在ViewController里面也重写:
用户手指触摸到了UIView上,由于我们没有重写UIView的UITouchEvent,所以他里面和super执行的一样的,将该事件继续分发到UIViewController;
UIViewController的TouchBegan被我们重写了,如果我们不super,那么我们在这里写响应代码。事件到这里就不继续分发了。可想而知,UIViewController祖先节点:UIWindow,UIApplication,AppDelegate都无权被分发此事件。
如果我们super了TouchBegan,那么此次触摸事件由
ViewController分发给UIWindow,
UIWindow继而分发给UIApplication,
UIApplication再分发给AppDelegate,
于是我们在ViewController和appDelegate的touchBegan方法中都捕获到了这次事件。
但是这只是处理点击事件顺序,也即是确认了第一响应者之后的处理流程。
那寻找第一响应者的流程是怎样的呢。
其实就是逆向走一遍,当沿着这条响应者链找到了第一响应者,那么就会返回事件给自己的nextResponder去处理,直到appDelegate。
下面回到咱们的主题,既然点击按钮,没有触发点击事件,响应者链有很大嫌疑。
为什么这么说呢??
我问大家,如果你点击完按钮之后,没有找到第一响应者,或者是第一响应者找错了,还会调用button的触发事件吗??显而易见。
下面列举常见的几大原因,有兴趣的同学可以去试试。
这个理解起来就简单一些,比如实现按钮点击事件所在的视图控制器被回收了。但是由于按钮被添加到当前可见视图上,按钮没有被回收,所以是可见的。但是点击这个按钮却没有任何作用。
单单是上面两个方面并不能涵盖所有的类似情况,而且这节咱们只是从UIButton入手。在具体的开发中,有很多经验所不能够解释的现象,但是上面两点一般是优先考虑的。