您的位置:首页 > 移动开发 > IOS开发

iOS响应者链

2016-05-25 13:49 302 查看
(一)

首先,当发生事件响应时,必须知道由谁来响应事件。

在IOS中,由响应者链来对事件进行响应,所有事件响应的类都是UIResponder的子类,响应者链是一个由不同对象组成的层次结构,其中的每个对象将依次获得响应事件消息的机会。当发生事件时,事件首先被发送给第一响应者,第一响应者往往是事件发生的视图,也就是用户触摸屏幕的地方。事件将沿着响应者链一直向下传递,直到被接受并做出处理。

一般来说,第一响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象viewcontroller(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top
view)到窗口(UIWindow对象)再到程序(UIApplication对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。

view1(第一响应者)-->viewController-->(superView)view2-->viewController--> (superView)view3...-->topView...

一个典型的相应路线图如:

First Responser -- > The Window -- >The Application -- > App Delegate

正常的响应者链流程经常被委托(delegation)打断,一个对象(通常是视图)可能将响应工作委托给另一个对象来完成(通常是视图控制器ViewController),这就是为什么做事件响应时在ViewController中必须实现相应协议来实现事件委托。在iOS中,存在UIResponder类,它定义了响应者对象的所有方法。UIApplication、UIView等类都继承了UIResponder类,UIWindow和UIKit中的控件因为继承了UIView,所以也间接继承了UIResponder类,这些类的实例都可以当作响应者。

一、事件分类

对于IOS设备用户来说,他们操作设备的方式主要有三种:触摸屏幕、晃动设备、通过遥控设施控制设备。对应的事件类型有以下三种:

1、触屏事件(Touch Event)

2、运动事件(Motion Event)

3、远端控制事件(Remote-Control Event)

今天以触屏事件(Touch Event)为例,来说明在Cocoa Touch框架中,事件的处理流程。首先不得不先介绍响应者链这个概念:

二、响应者链(Responder Chain)

先来说说响应者对象(Responder
Object),顾名思义,指的是有响应和处理事件能力的对象。响应者链就是由一系列的响应者对象构成的一个层次结构。

UIResponder是所有响应对象的基类,在UIResponder类中定义了处理上述各种事件的接口。我们熟悉的UIApplication、
UIViewController、UIWindow和所有继承自UIView的UIKit类都直接或间接的继承自UIResponder,所以它们的实例都是可以构成响应者链的响应者对象。图一展示了响应者链的基本构成:



图一

从图一中可以看到,响应者链有以下特点:

1、响应者链通常是由视图(UIView)构成的;

2、一个视图的下一个响应者是它视图控制器(UIViewController)(如果有的话),然后再转给它的父视图(Super
View);

3、视图控制器(如果有的话)的下一个响应者为其管理的视图的父视图;

4、单例的窗口(UIWindow)的内容视图将指向窗口本身作为它的下一个响应者

需要指出的是,Cocoa Touch应用不像Cocoa应用,它只有一个UIWindow对象,因此整个响应者链要简单一点;

5、单例的应用(UIApplication)是一个响应者链的终点,它的下一个响应者指向nil,以结束整个循环。

三、事件分发(Event Delivery)

第一响应者(First responder)指的是当前接受触摸的响应者对象(通常是一个UIView对象),即表示当前该对象正在与用户交互,它是响应者链的开端。整个响应者链和事件分发的使命都是找出第一响应者。

UIWindow对象以消息的形式将事件发送给第一响应者,使其有机会首先处理事件。如果第一响应者没有进行处理,系统就将事件(通过消息)传递给响应者链中的下一个响应者,看看它是否可以进行处理。

iOS系统检测到手指触摸(Touch)操作时会将其打包成一个UIEvent对象,并放入当前活动Application的事件队列,单例的UIApplication会从事件队列中取出触摸事件并传递给单例的UIWindow来处理,UIWindow对象首先会使用hitTest:withEvent:方法寻找此次Touch操作初始点所在的视图(View),即需要将触摸事件传递给其处理的视图,这个过程称之为hit-test
view。

UIWindow实例对象会首先在它的内容视图上调用hitTest:withEvent:,此方法会在其视图层级结构中的每个视图上调用pointInside:withEvent:(该方法用来判断点击事件发生的位置是否处于当前视图范围内,以确定用户是不是点击了当前视图),如果pointInside:withEvent:返回YES,则继续逐级调用,直到找到touch操作发生的位置,这个视图也就是要找的hit-test
view。

hitTest:withEvent:方法的处理流程如下:

首先调用当前视图的pointInside:withEvent:方法判断触摸点是否在当前视图内;

若返回NO,则hitTest:withEvent:返回nil;

若返回YES,则向当前视图的所有子视图(subviews)发送hitTest:withEvent:消息,所有子视图的遍历顺序是从最顶层视图一直到到最底层视图,即从subviews数组的末尾向前遍历,直到有子视图返回非空对象或者全部子视图遍历完毕;

若第一次有子视图返回非空对象,则hitTest:withEvent:方法返回此对象,处理结束;

如所有子视图都返回非,则hitTest:withEvent:方法返回自身(self)。



图二

加入用户点击了View E,下面结合图二介绍hit-test view的流程:

1、A是UIWindow的根视图,因此,UIWindwo对象会首相对A进行hit-test;

2、显然用户点击的范围是在A的范围内,因此,pointInside:withEvent:返回了YES,这时会继续检查A的子视图;

3、这时候会有两个分支,B和C:

点击的范围不再B内,因此B分支的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;

点击的范围在C内,即C的pointInside:withEvent:返回YES;

4、这时候有D和E两个分支:

点击的范围不再D内,因此D的pointInside:withEvent:返回NO,对应的hitTest:withEvent:返回nil;

点击的范围在E内,即E的pointInside:withEvent:返回YES,由于E没有子视图(也可以理解成对E的子视图进行hit-test时返回了nil),因此,E的hitTest:withEvent:会将E返回,再往回回溯,就是C的hitTest:withEvent:返回E--->>A的hitTest:withEvent:返回E。

至此,本次点击事件的第一响应者就通过响应者链的事件分发逻辑成功的找到了。

不难看出,这个处理流程有点类似二分搜索的思想,这样能以最快的速度,最精确地定位出能响应触摸事件的UIView。

三、说明

1、如果最终hit-test没有找到第一响应者,或者第一响应者没有处理该事件,则该事件会沿着响应者链向上回溯,如果UIWindow实例和UIApplication实例都不能处理该事件,则该事件会被丢弃;

2、hitTest:withEvent:方法将会忽略隐藏(hidden=YES)的视图,禁止用户操作(userInteractionEnabled=YES)的视图,以及alpha级别小于0.01(alpha<0.01)的视图。如果一个子视图的区域超过父视图的bound区域(父视图的clipsToBounds
属性为NO,这样超过父视图bound区域的子视图内容也会显示),那么正常情况下对子视图在父视图之外区域的触摸操作不会被识别,因为父视图的pointInside:withEvent:方法会返回NO,这样就不会继续向下遍历子视图了。当然,也可以重写pointInside:withEvent:方法来处理这种情况。

3、我们可以重写hitTest:withEvent:来达到某些特定的目的,下面的链接就是一个有趣的应用举例,当然实际应用中很少用到这些。

(二)

文/蜗牛B(简书作者)

原文链接:http://www.jianshu.com/p/10a01fbea978

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

很多类型事件的传递都依赖于响应者链。响应者链是一系列响应者对象的关系集。它始于第一响应者而终于Application对象。如果第一响应者不能处理这个事件,它会将事件传递给响应者链中得下一个响应者。

第一响应者是一个可以响应并处理事件的对象。UIResponder就是所以响应者对象的基类,它定义了一些通用的接口并不仅仅只是事件的处理还有普遍的响应者行为。UIApplication,UIcontroller,和UIview类的实例都是响应者,这就意味所有的view和大多数关键的controller对象都是响应者。值得注意的是核心动画图层Core Animation layers不是响应者。(看看它们都是继承谁就就知道啦)

第一响应者被设计为优先获得处理事件的能力。比较典型的是,第一响应者是view对象,一个响应者对象要成为第一响应者必须经过下面两件事:

1. 覆盖canBecomeFirstResponder方法并且返回YES

2. 收到becomeFisrtResponder消息。如果有必要的话,响应者对象可以给自己发送这样的消息。

注意:指定第一响应者对象之前一定要确认你的app已经被建立。例如,你比较典型的调用becomeFirstRespnder方法在viewDidAppear:方法中。如果你尝试着在viewWillAppear:去指定第一响应者,你的对象绘制还没有被建立,以至于becomeFirstResponder方法会返回NO。

事件并不是唯一依赖响应者链的对象。响应者链通常被使用宇一下所有的情况:

触摸事件:如果命中测试view不能处理触摸事件,这个事件就会被传递到响应者链中命中view的上一个响应者。

手势事件:使用UIKit去处理震动手势事件,第一响应者必须要么实现UIResponderd的motionBegain:withEvent:方法要么实现motionEnded:withEvent:方法

远程控制事件:为了去处理“远程控制事件”,第一响应者必须实现UIResponder的remoteControlReceivedWithEvent:方法。

行为消息:当用户操作一个控制,例如一个button或者switch,并且行为方法的目标是nil,这个消息将会被通过响应者链从控制器view开始发送。

如果initial object(初始对象)命中测试view或者第一响应者不能处理该事件,UIKit会递交事件给响应者链中的下一个响应者。每一个响应者都有权决定它是否想要去处理该事件或者是继续递交给它自己的下一个响应者通过调用nextResponder方法。这个过程将会持续到某个响应者处理该事件或者没有更多的响应者为止。

响应者链队列始于iOS侦测事件并且递交她给initial对象,那是一个典型的view。这个initialview具备优先处理事件的权利,如图:2-2显示了两个不同的应用构造的两个不同的事件传递路径。一个app的事件传递路径取决于它自身的构造,但是所有的事件传递都遵循相同的传递规则。

图:2-2 iOS响应者链



对于左边的app,事件传递按下面的路径:

1. Initial view 尝试着去处理事件或者消息。如果不能处理事件,它就递交事件给superview,因为这个initial view并不是视图控制器层级中得顶级view.

2. 这个superview尝试去处理该事件,如果superview不能处理该事件,它就递交事件给它的父view,因为它也不是view层级的顶级view。

3. 视图控制器的顶级view尝试着去处理该事件,如果连顶级view都不能处理该事件,它就递交事件给它的controller。

4. 这个viewcontroller尝试着去处理该事件,并且如果它不能处理该事件,它就会递交事件给window。

5. 如果window不能处理该事件,它就递交事件给singlegon app object(既UIApplication)

6. 如果连application都不能处理该事件,那么毫无疑问该事件将会被丢弃。

右边的应用传递按照稍微不同的路径,但是所有的事件传递都遵循相同的传递规则:

1. 一个view controller层级中得view向上递交事件知道它到达顶级view。

2. 顶级view递交事件给它的controller。

3. Viewcontroller递交事件给它的顶级view的superview,步骤1-3重复直到它到达rootcontroller。

4. 这个rootviewcontroller递交事件给window对象。

5. window对象递交事件给application对象。

(三)


总结:

事件的传递和响应分两个链:

传递链:由系统向离用户最近的view传递。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
响应链:由离用户最近的view向系统传递。initial view –> super view –> …..–> view controller –> window –> Application
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: