iOS事件传递

前言

无论是Android,还是IOS,都是事件驱动的操作系统,事件是操作系统的灵魂。却很少有人能够理清楚事件在操作系统内部是如何进行传递处理的。这篇文章将深入探讨iOS系统事件,阐述事件是如何在iOS系统内部进行传递并处理的,希望这篇文章能够对你有所帮助。

代码说明

本文将使用Objective-C语言进行编码,使用Cocoapods作依赖管理。同时,使用了一个非常优秀的自动布局库PureLayout。PureLayout是拥有非常优秀的自动布局API,支持iOS和OS X双系统,强烈推荐大家使用。

基本原理

事件的分类

iOS系统将事件分为三类:

  • Multitouch Events
  • Motion Events
  • Remote Control Events

Multitouch Events: 所谓的多点触摸事件,非常好理解,即用户触摸屏幕交互产生的事件类型。

Motion Events: 所谓的移动事件。是指用户在摇晃,移动和倾斜手机的时候产生的事件成为移动事件。这类事件依赖于iPhone手机里面的加速计,陀螺仪等传感器。

Remote Control Events:所谓的远程控制事件。这个事件从名称上面看,不太好理解。但其实,这个事件指的是用户在操作多媒体的时候产生的事件。比如,播放音乐、视频等。

仔细分析这三类事件,Multitouch Events有明确的触摸视图,UIKit框架的View对象可以明确获取到当前点击的视图对象以及坐标。然后,对触摸视图做出相应的响应。而Motion EventsRemote Control Events却没有一个明确的交互界面的概念。iOS系统为了支持对这类事件的响应,提出了Responder的概念。
关于Responder,我们后面再来探讨。

鉴于系统对这三类事件处理的区别,我们将这三类事件区分为两类:

  • Multitouch Events 有明确的交互界面,可以获取到当前点击的视图组件,并作出相应的响应。
  • Motion Events and Remote Control Events 没有明确的交互界面,依赖于Responder对事件作出相应的响应

Continue
首先,让我们来了解一下Responder的概念,什么是Responder,怎样才能成为Responder,Responder又是如何对事件作出响应的。

About Responder

  1. 什么是Responder
    ResponderUIKit框架封装的一个对象类型,它可以响应并处理事件。所有Responder对象的基类都是UIResponder,下面我们来通过一张类图看看哪些对象具有Responder特性
    图-1

    从上图可以看出,UIApplicationUIViewControllerUIView都是UIResponder对象,都具有对事件进行响应,处理的能力

再来看看UIResponder类里面的一些方法和属性

- (UIResponder* )nextResponder;


- (BOOL)canBecomeFirstResponder;    // default is NO
- (BOOL)becomeFirstResponder;


// Touch Event
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event;


// Motion Event
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);


// Remote Control Event
(void)remoteControlReceivedWithEvent:(UIEvent*)event NS_AVAILABLE_IOS(4_0);

从上面的代码片段,可以看到UIResponder对象可以处理TouchEventMotionEventRemote Control Event事件。还有一个非常重要的方法nextResponder,这个方法可以获取到下一个关联的ResponderResponder对象正是关联nextResponder引用组成了一个Responder链,我们称之为The Responder Chain,系统事件会沿着这个Responder Chain传播到nextResponder,直到最后一个Responder,如果依然没有处理该事件,事件就会被舍弃。但是,问题来了,系统必须先找到第一个Responder,即第一个可以响应该对象的事件。英文称之为First Responder

  1. 怎样才能成为First Responder
    这里还需要关注的一个概念就是First ResponderFirst Responder是第一个可以处理当前事件的对象,如果First Responder不能处理当前事件,则传递到nextResponder。然后依次传递给nextResponder

我们用一张官方的图,来具体地看看Responder是如何在响应链之间传递的

图-1

我们来分析一下,事件的传递过程:
第一步,Application对象从事件队列中获取事件对象
第二步,Application对象将事件对象传递给WindowWindow对象继续传递给First Responder,然后事件就会沿着Responder Chain(响应者链)传递,直到找到可以处理它的对象。

基于上面的讨论,事件的传递过程,我们基本已经讨论清楚。但是,仅仅是讨论还不够,我们缺乏必要的论据,关于事件传递的证明,我会在后面给出代码证明。

这里,我们先来关注一个问题:
对于TouchEvents事件,系统是如何获取到当前正在点击的视图对象的呢?(其实就是寻找First Responder
这依赖于UIView里面的两个方法:

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;  
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

第一个方法会返回当前点击的View对象,第二个方法判断当前点击的坐标是否在当前视图边界范围内。如果在当前视图范围内,则返回YES,否则,返回NO。第一个方法会根据第二个方法的判断返回当前点击的视图。

下面我们通过一个比较直观的图形来讲述iOS系统获取当前点击视图对象的过程:


图-2

假设用户点击了视图D:

  1. 检测到点击坐标在View A范围之内。
  2. 继续检测点击范围是否在其子视图B,C范围内。发现点击范围在视图C范围内,则忽略掉B视图及其子视图分支。
  3. 继续检测点击范围是否在其子视图D范围内,如果是,则用户当前视图即为视图D。如果不是,继续检测其子视图。

根据上述分析:iOS系统会从父视图向子视图依次查找,直到找到点击范围在当前视图边界范围以内。如果点击范围在某子视图范围内,并且没有了子视图,则该视图即为当前点击视图。如果点击范围在某子视图范围之内,并且不在其子视图范围之内,则点击视图即为当前点击视图。

PS:感兴趣的同学可以自己去证明一下点击视图的查找过程。

下面开始事件传递的证明:


图-3

为此我们创建如上图所示的一个简单app,三种颜色View分别对应类
红色:FirstView
绿色:SecondView
蓝色:ThirdView

我们以Remote Control Events为例,来看看事件的传递过程:
首先,我们让SecondView成为First Responder

- (BOOL)canBecomeFirstResponder {
  return YES;
}
// 然后在ViewController中,执行如下代码
[_secondView becomeFirstResponder];

然后,在三个视图分别添加如下测试语句:
FirstView

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
            NSLog(@"FirstView:Play");
            break;
            
        default:
            break;
    }
}

SecondView

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
            NSLog(@"SecondView:Play");
            break;
            
        default:
            break;
    }
}

ThirdView

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    switch (event.subtype) {
        case UIEventSubtypeRemoteControlPlay:
            NSLog(@"ThirdView:Play");
            break;
            
        default:
            break;
    }
}

运行,从屏幕底部拉出音频播放界面,点击播放。这里会触发Remote Control Event,回调上图中的方法。

图-4

看到如下输出日志:

EventDelivery[4559:323237] SecondView:Play

注释掉SecondView里面的 remoteControlReceivedWithEvent:,我们可以看到如下输出日志:

EventDelivery[4559:323237] ThirdView:Play

这里的现象说明:Remote Control Event会沿着父视图往子视图传递,即父视图的nextResponder就是其子视图。这里大家可以考虑一下,如果当前视图有多个直接的子视图呢?nextResponder会是哪一个?请读者们自行证明。

这里,我们更进一步证明,事件是否会从View传递给ViewController,注释掉三个视图里面的remoteControlReceivedWithEvent:方法,并在ViewController中重写该方法,同时添加打印日志输出,运行,可以看到如下日志输出:

EventDelivery[4559:323237] ViewController:Play

这更进一步证明了Responder的传递方向符合上面的分析。

总结

iOS系统将事件分为三类:

  • Touch Events
  • Motion Events
  • Remote Control Events

根据三类事件获取First Responder方式的不同,又可以将事件分为:

  • Touch Events
  • Motion Events and Remote Control Events

第一类事件通过获取当前用户交互的界面组件,即为First Responder
第二类事件的First Responder由用户手动指定。

成为First Responder必须实现如下两个步骤:

  • 重写canBecomeFirstResponder方法,返回YES
  • UIResponder对象发送becomeFirstResponder消息

综上所述,事件的传递过程可以分为两步:
第一步,获取到First Responder,不同的事件有不同的获取方式。
第二步,从First Responder沿着Responder Chain传递到nextResponder,直到事件被处理或者舍弃。

常见的Responder传递方向有:
Initial View -> Parent View -> ViewController -> Window -> Application

如果最终传递到Application对象,依然没有对事件作出响应,事件就会被舍弃掉。

通常来说,子视图的nextResponder即为其父视图。如果子视图直接依附于ViewController,则该子视图的nextResponder即为其依附的ViewController

PS:本文源码和文章原稿都在下方我的Github仓库中,有任何问题请按照下面的方式联系我。

参考资料:Event Handling Guide for iOS

如果你喜欢这篇文章,请到Fork我的github仓库:
https://github.com/yuanhoujun/jianshu.git
如果你对这篇文章有任何的修改建议,请给我发送Pull Request。如果你想给我留言,可以加我的QQ:626306805,如果你想和更多的人一起讨论iOS,请加入iOS交流群:468167089

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

推荐阅读更多精彩内容

  • 转至(https://www.oschina.net/question/1777276_2132459) 前言 无...
    码农耕阅读 247评论 0 0
  • 用户以多种方式操纵他们的iOS设备,例如触摸屏幕或摇动设备。 iOS会解释用户何时以及如何操作硬件并将此信息传递到...
    坤坤同学阅读 3,971评论 7 19
  • 本篇主要讲解iOS事件传递的整个过程,大部分内容翻译自Apple Developer Guide,原文链接 当一个...
    Little_Mango阅读 1,261评论 4 10
  • 事件传递:响应者链 当你设计一个app的时候,你很可能需要你的app能够动态响应某些事件。比如,触摸可以发生在屏幕...
    hjfrun阅读 1,013评论 1 5
  • 本次笔记主要是整理一下关于 iOS 中关于事件传递和响应机制,参考了一些其他资料加上自己的理解。 事件 Event...
    varlarzh阅读 321评论 0 2