事件处理原理(iOS篇) by sixleaves

就以该文作为简书博客的开端吧, 本人博客园的博客http://www.cnblogs.com/objectc/, 以后这两边博客会保持同步更新, 这几天会把上面的博客都同步过来.

前言

了解IOS事件处理的本质关键要先掌握几个概念。首先是事件的派发(Event Delivery)的过程, 一个是响应者链条如何构成

事件的派发:

Q1: 你又没有想过,如果你一个屏幕中有多个的View。当你点击某个view的时候, 这个点击事件是如何传递到这个View身上的?

S1: 正是因为当我们点击屏幕上某个点的时候, IOS会检查到手指触摸操作(Touch),并生产一个UITouch对象,将其打包成一个UIEvent对象。然后将其放入当前活动的Application的事件对列, UIApplication会从事件对列中按照对列的顺序,取出触摸事件传递给UIWindow处理,UIWindow对象会使用hitTest:withEvent:方法来寻找此次的触摸操作初始点所在的最深层次的视图(View).
**即调用hitTest:withEvent会返回该触摸点所在的最深层次的视图。 **

Q2:hitTest:withEvent如何实现找到最深层次的视图,也就是目的视图。

S2: 这就要说到深度优先搜索算法,hitTest:withEvent正是基于深度优先搜索的方式来找到最深层次的视图对象。所以我来介绍以下深度优先算法的思想, 要理解该思想, 你首先要有树结构这一概念(参见数据结构中的树结构)。该思想是从根节点开始遍历树,而遍历的顺序是采用把下一个子节点当做当前根节点继续遍历。
所以其是先遍历到最树的最深的一层再层层回朔到根节点,接着在把另外一个子节点当做当前根节点继续遍历。正是基于这种思想, 所以我们可以很方便的采用递归来实现。
如果还是不理解,有两种办法帮助你,一种是去找深度优先的动态图,一看就懂了我说的。另外一种方法是去复习数据结构与算法。

Q3:既然hitTest:withEvent利用了深度优先的思想来做,并采用递归的做法来做。那么递归的条件是啥?也就是说什么条件下事件不会向下派发?

S3:根据官方文档给出的条件是(hidden == YES || userInteractionEnabled == YES || alpha < 0.01 || subViews.bounds > subViews.superView.bounds)

何为响应者链条

Q4: 也许你经常听别人在说响应者链条,但是还是云里雾里。这边我就给你解释下

S4: 首先先明确何为响应者? ===> 在ios开发中继承自UIResponder的类或子类就是响应者,顾明思意,响应者是用来相应事件的(触摸事件、运动事件、远程遥控事件)。所以所谓的响应者链条就是一系列响应者构成的层次结构。

Q5: 那么响应者链条是如何表示这种层次结构的呢?

S5: 响应者链条是通过nextResponder方法的返回值来组成这种层次结构的 ,苹果有一段官方解释如下:

The UIResponder class does not store or set the next responder automatically, instead returning nil by default. Subclasses must override this method to set the next responder. UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t); UIViewController implements the method by returning its view’s superview; UIWindow returns the application object, and UIApplication returns nil.

也就是说,响应者对象是不会自动设置和存储下一个响应者,默认情况下是直接返回nil。而继承自UIResponder的子类必须重写这个方法来设置下一个响应者,并且需要遵循如下规范

    1. 如果子类是UIView,那么其getter方法的nextResponder必须返回其UIViewController对象。
       如果不存在控制器,则返回其父视图对象。
    2. 如果子类是UIViewController对象, 那么重写的nextResponder方法必须返回其view视图的父视图对象。
    3. 如果子类是UIWindow对象,那么重写的nextResponder方法返回的是application对象
    4. 如果子类是UIApplication对象,那么重写的nextResponder方法,返回nil。

通过上述规范,结合下图,你应该能很容易理解所谓的响应者链条如何构成:

响应者链条.jpeg

Q6: 疑问,假设我有一个自定义的SWPButton,而且给其设置了连线action(也就是点击按钮后回调的函数)。我们知道当我们点击按钮的时候系统会捕捉到这个事件,并将其派发下来。那么我们如何做到让按钮不响应这个action而是只响应这个事件。

  • S6: 本质上系统默认实现的touchesBegan:withEvent:方法是做了两件事件, 活着所所有继承子UIResponder的系统定义的类,都默认实现了以下两件事情
    • 第一件事情是: 调用[super touchesBegan:withEvent]让父类有机会来处理该事件。 同时传递了事件,让父类能根据事件处理相应的action回调,比如父类会在这里面处理action(也就是点击按钮时候的回调)
    • 第二件事情是: 抛给上一个响应者,让上一个响应者也能处理该事件。同时传递了该事件。
      这两个是不一样的,前者是针对父类,后者是针对响应者链条。二者缺一不可。
      再者,所以有的人会将只要实现了[super toucesBegan:withEveent]就会将事件抛开上一个响应者,这是不严谨的说法。因为这是因为系统已经为所有继承自UIResponder的子类,做了上面那两件事件。
      比如 ,当你自定义一个SWPButton该类继承自UIButton,那么当你重写touchesBegan方法的时候,在你们调用[super touchesBegan:withEvent]这个方法时候,系统自定义的UIButton类已经具备这个功能,所以
      其能完成向上抛。而当我们定义一个纯洁的SWPButton直接继承自UIResponder, 那么我们必须自己实现向上抛这个事情,而不是只调用[super touchesBegan:withEvent]。
      <hr />
      响应者链条.jpeg
      华丽的分割线,以下是度对Q6的解答
      因为我们继承自UIButton,所以要截断该action的回调,我们只需重写touchesBegan:withEvent方法。并且不做第一件事件,那么也就不会将事件传递给Button对象,button也就无法响应相应的action。
#import "SWPButton.h"
@implementation SWPButton
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    [self.nextResponder touchesBegan:touches withEvent:event];
}
@end

所以此时如果你点击按钮, 其会先调用这个自定义按钮的touchesBegan, 因为点击事件传递到了button身上,所以会调用touchesBegan来响应该事件,但是该事件不在交给父类处理,所以不会调用action。只会继续将其抛给上一个响应者。

放大招(根据View来找到其所在控制器):

@implementation UIView (ParentController)
-(UIViewController*)parentController{
    UIResponder *responder = [self nextResponder];
    while (responder) {
    if ([responder isKindOfClass:[UIViewController class]]) {
        return (UIViewController*)responder;
    }
    responder = [responder nextResponder];
    }
    return nil;
}
@end
思路很简单,就是利用响应者链条来寻找UIViewController.
那么这个UIView的上一个响应者只有两种情况,一种是依然是一个UIView对象或其子对象,也就是说它是这个UIView的子View。
一种是这个  UIView的控制器。所以我们才需要循环判断,然后不断找(循环),直到第一次找到的控制器,就是这个UIView所在的控制器。
如果找不到,就返回nil。

是不是更加理解你事件派发的过程,所谓的事件派发过程,其实就是寻找最合适的视图的时候,事件随着这个寻找过程,不断传递。
为什么要传递UIEvent呢?因为通过它给以获得Touch对象,而通过Touch对象我们可以获得初始触摸点。也就是说hitTest:withEvent主要实现的功能是,传递事件,找到最合适的视图。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容