iOS基础补完计划--透过堆栈看事件响应机制

栏目: IOS · 发布时间: 5年前

内容简介:目录前言众所周知、手势识别器的权限比响应链更高。

目录

  • 前言

  • TouchEvent 响应链

  • UIControl

  • UIGestureRecognizer

  • 结论

前言

众所周知、手势识别器的权限比响应链更高。

而UIControl的响应机制不涉及响应链、由UIAPPlication直接分发。

于是、会出现一些冲突的问题。

比如给self.view添加手势之后、cell不可点击、但UIButton不受影响。

可以看看 《iOS点击事件和手势冲突》

iOS基础补完计划--透过堆栈看事件响应机制

而在网上找了找、但总觉得不那么直观。

TouchEvent 响应链

点击一个View

frame #20: 0x000000010ce0e4fd NSObject`-[View1 touchesBegan:withEvent:](self=0x00007f9c19e20a10, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x000060400010d410) at View1.m:107
frame #21: 0x000000010ec87e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052
frame #22: 0x000000010ec897c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #23: 0x000000010ec2d310 UIKit`-[UIApplication sendEvent:] + 352
  • 结论

  1. 响应链由UIWindow的_sendTouchesForEvent方法调起。

  2. 而_sendTouchesForEvent由sendEvent:方法调起。

UIControl

点击一个UIButton

  • UIButton响应线

frame #0: 0x000000010ce0d2a7 NSObject`-[ViewController btnClick](self=0x00007f9c19f0f080, _cmd="btnClick") at ViewController.m:55
frame #1: 0x000000010ec133e8 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 83
frame #2: 0x000000010ed8e7a4 UIKit`-[UIControl sendAction:to:forEvent:] + 67
frame #3: 0x000000010ed8eac1 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 450
frame #4: 0x000000010ed8da09 UIKit`-[UIControl touchesEnded:withEvent:] + 580
frame #5: 0x000000010ec880bf UIKit`-[UIWindow _sendTouchesForEvent:] + 2729
frame #6: 0x000000010ec897c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #7: 0x000000010ec2d310 UIKit`-[UIApplication sendEvent:] + 352
  • 响应链开始线

frame #0: 0x000000010c9db48b NSObject`-[View1 touchesBegan:withEvent:](self=0x00007f8a15405cc0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000001044a0) at View1.m:109
frame #1: 0x000000010e854e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052
frame #2: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #3: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
  • 响应链结束线

frame #0: 0x000000010c9db60b NSObject`-[View1 touchesEnded:withEvent:](self=0x00007f8a15405cc0, _cmd="touchesEnded:withEvent:", touches=1 element, event=0x00006000001044a0) at View1.m:120
frame #1: 0x000000010e8550bf UIKit`-[UIWindow _sendTouchesForEvent:] + 2729
frame #2: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #3: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
  • 结论

  1. 会由UIWindow的_sendTouchesForEvent:调起响应链。

  2. 在touchesBegan:withEvent判断如果为UIControl、则截断响应链(不向next reponder传递)、并且尝试识别。

  3. 识别成功则直接由UIApplication向target发送Action事件。

UIGestureRecognizer

点击一个添加了手势的View或者SubView

  • 手势响应线

frame #0: 0x00000001034ea447 NSObject`-[ViewController doTapChange](self=0x00007fbb31607910, _cmd="doTapChange") at ViewController.m:101
frame #1: 0x00000001058e754f UIKit`-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 57
frame #2: 0x00000001058f0324 UIKit`_UIGestureRecognizerSendTargetActions + 109
frame #3: 0x00000001058edb6c UIKit`_UIGestureRecognizerSendActions + 307
frame #4: 0x00000001058ecdc0 UIKit`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 859
frame #5: 0x00000001058d1e24 UIKit`_UIGestureEnvironmentUpdate + 1329
frame #6: 0x00000001058d18a7 UIKit`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 484
frame #7: 0x00000001058d09a9 UIKit`-[UIGestureEnvironment _updateGesturesForEvent:window:] + 281
frame #8: 0x00000001053667ab UIKit`-[UIWindow sendEvent:] + 4064
frame #9: 0x000000010530a310 UIKit`-[UIApplication sendEvent:] + 352
  • 响应链开始线

[ViewController touchesBegan:withEvent:](self=0x00007f8a15426010, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x00006000001044a0) at ViewController.m:97
frame #1: 0x000000010ea0c767 UIKit`forwardTouchMethod + 340
frame #2: 0x000000010ea0c602 UIKit`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x000000010e854e1a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052
frame #4: 0x000000010e8567c1 UIKit`-[UIWindow sendEvent:] + 4086
frame #5: 0x000000010e7fa310 UIKit`-[UIApplication sendEvent:] + 352
  • 响应链取消线

frame #0: 0x00000001034eb5ed NSObject`-[View1 touchesCancelled:withEvent:](self=0x00007fbb314061e0, _cmd="touchesCancelled:withEvent:", touches=0x000060400025a1f0, event=0x000060000011d0a0) at View1.m:120
frame #1: 0x0000000105303434 UIKit`__98-[UIApplication _cancelViewProcessingOfTouches:withEvent:sendingTouchesCancelledToViewsOfTouches:]_block_invoke + 663
frame #2: 0x0000000105302c22 UIKit`-[UIApplication _cancelTouches:withEvent:includingGestures:notificationBlock:] + 1091
frame #3: 0x0000000105303167 UIKit`-[UIApplication _cancelViewProcessingOfTouches:withEvent:sendingTouchesCancelledToViewsOfTouches:] + 158
frame #4: 0x00000001058d41e8 UIKit`_UIGestureEnvironmentCancelTouches + 687
frame #5: 0x00000001058d3f26 UIKit`-[UIGestureEnvironment _cancelTouches:event:] + 42
frame #6: 0x00000001058ed2d0 UIKit`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 2155
frame #7: 0x00000001058d1e24 UIKit`_UIGestureEnvironmentUpdate + 1329
frame #8: 0x00000001058d18a7 UIKit`-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 484
frame #9: 0x00000001058d09a9 UIKit`-[UIGestureEnvironment _updateGesturesForEvent:window:] + 281
frame #10: 0x00000001053667ab UIKit`-[UIWindow sendEvent:] + 4064
frame #11: 0x000000010530a310 UIKit`-[UIApplication sendEvent:] + 352
  • [KTUITapGestureRecognizer touchesBegan:withEvent:]线:

-[KTUITapGestureRecognizer touchesBegan:withEvent:](self=0x00006000003d6700, _cmd="touchesBegan:withEvent:", 0x0000600000dd1d40) ITapGestureRecognizer.m:14
-[UIGestureRecognizer _touchesBegan:withEvent:] + 240
-[UITouchesEvent _sendEventToGestureRecognizer:] + 287
-[UIGestureEnvironment _updateForEvent:window:]_block_invoke + 64
-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 276
-[UIGestureEnvironment _updateForEvent:window:] + 200
-[UIWindow sendEvent:] + 4058
-[UIApplication sendEvent:] + 352
  • 然后来科普一下手势的识别过程

Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。

关于谁先谁后。佐证的话、你可以自定义一个子类并且重载一些方法、这里直接贴结果:

大致理解是,Window在将事件传递给hit-tested view之前,会先将事件传递给相关的手势识别器并由手势识别器优先识别。若手势识别器成功识别了事件,就会取消hit-tested view对事件的响应;若手势识别器没能识别事件,hit-tested view才完全接手事件的响应权。

佐证的话、你可以自定义一个子类并且重载一些方法、这里直接贴结果

先用一个离散型手势做实验:

14:20:17-[KTUITapGestureRecognizer touchesBegan:withEvent:]
14:20:17-[KTUITapGestureRecognizer2 touchesBegan:withEvent:]
14:20:17-[View2 touchesBegan:withEvent:]
14:20:17-[View touchesBegan:withEvent:]
14:20:18-[KTUITapGestureRecognizer touchesEnded:withEvent:]
14:20:18-[KTUITapGestureRecognizer2 touchesEnded:withEvent:]
14:20:18-[View2 tapAction]
14:20:18-[View2 touchesCancelled:withEvent:]
14:20:18-[View touchesCancelled:withEvent:]

而对于持续型手势

在一开始滑动的过程中,手势识别器处在识别手势阶段,滑动产生的连续事件既会传递给手势识别器又会传递给View,因此View的 touchesMoved:withEvent:在开始一段时间内会持续调用;

当手势识别器成功识别了该滑动手势时,手势识别器的action开始调用,同时通知Application取消View对事件的响应。之后仅由滑动手势识别器接收事件并响应,View不再接收事件。

这里有几个点可以说说:

  1. 可以看到右侧有一秒的时间差

    也就是说View的后续动作会等待GestureRecognizer的识别结果。

  2. KTUITapGestureRecognizer以及KTUITapGestureRecognizer2的方法都被触发了

    也就是说是hit-tested列表中所有View上的手势识别器都会得到机会去识别事件。(依赖UITouch中的gestureRecognizers属性)

  3. 至于最后触发谁、取决于代理中的设置。默认按照响应链的顺序。

并且手势在触摸事件处理流程中,处于观察者的角色,其不是view层级结构的一部分,所以也不参与或者依赖responder chain(你把上层View的touch不调用super也影响不了)。

其流程大概如下图所示:

iOS基础补完计划--透过堆栈看事件响应机制

注:图中view与手势的关系是,手势关联在view或view的superview(可能多级)上。

  • 结论

  1. 手势的Action由UIGestureRecognizer直接调用。

  2. 响应链的开始依旧由UIWindow的_sendTouchesForEvent触发。

  3. 响应链的cancel由UIGestureEnvironment发起

  4. 手势的识别(KTUITapGestureRecognizer的touchesBeagn)并不依赖响应链(View的touchesBeagn)、而是由[UIWindow sendEvent:]方法中被UIGestureEnvironment直接调起。

结论

iOS基础补完计划--透过堆栈看事件响应机制

有几个地方可以提一下:

  1. 响应链本身什么都不做

    所以手势以及UIControl的处理本身并不依赖响应链。以下是勘误:

    对于UIView是这样。但当我看过UIControl后有了新的理解~

    但对于UIControl。如果注册了Action、那么UIContrl便会在自己的位置上截断响应链、并且TouchBegan等方法内部处理TouchDown等等状态以及对应的Action。

    此外需要注意的是:只要注册了Action、就会截断响应链(但并不一定会触发-sendAction:to:forEvent:)。哪怕是向一个UIButton注册EditingDidEnd。

    由于UIControl依赖touchBegan并且具有截断的特性、所以位于下方的另一个UIButton (如果你没

    搞什么特殊的事情)才永远不会被触发。

  2. 响应链必然会触发、只是后期处理不一样。

    如果是手势、则被取消。如果是UIControl则被结束。

    如果设置tap.delaysTouchesBegan = YES;在识别结果出来之前则不会触发View的touchesBegan。

  • 关于事件处理的优先级。

这个光看堆栈应该是看不出来的。

但结合一些结论、可以试着猜测:

  1. 目标View上有手势

    手势优先识别

  2. 目标View上没有手势但是归属系统UIControl

    UIControl优先处理

  3. 目标View就是个普通View(或者自定义的Control)

    下层手势优先识别、如果没有手势/识别失败则交给响应链

  • 如何将事件发送给指定的View/手势

全部依赖UITouch的属性

1.Window

@property(nullable,nonatomic,readonly,strong) UIWindow *window;

2.响应链/UIControl

@property(nullable,nonatomic,readonly,strong) UIView *view;

3.手势

@property(nullable,nonatomic,readonly,copy)   NSArray 
<uigesturerecognizer nbsp="">
  *gestureRecognizers NS_AVAILABLE_IOS(3_2);
</uigesturerecognizer>

最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果愿意补充以及不吝赐教小弟会更加感激。

作者:kirito_song

链接:https://www.jianshu.com/p/56c20736a73c


以上所述就是小编给大家介绍的《iOS基础补完计划--透过堆栈看事件响应机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First Rails

Head First Rails

David Griffiths / O'Reilly Media / 2008-12-30 / USD 49.99

Figure its about time that you hop on the Ruby on Rails bandwagon? You've heard that it'll increase your productivity exponentially, and allow you to created full fledged web applications with minimal......一起来看看 《Head First Rails》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具