美文网首页iOS开发攻城狮的集散地iOS DeveloperIOS
iOS基础补完计划--透过堆栈看事件响应机制

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

作者: kirito_song | 来源:发表于2018-09-05 11:08 被阅读28次

    目录

    • 前言
    • TouchEvent 响应链
    • UIControl
    • UIGestureRecognizer
    • 结论

    前言

    众所周知、手势识别器的权限比响应链更高。
    而UIControl的响应机制不涉及响应链、由UIAPPlication直接分发。

    于是、会出现一些冲突的问题。
    比如给self.view添加手势之后、cell不可点击、但UIButton不受影响。
    可以看看《iOS点击事件和手势冲突》

    而在网上找了找、诸如《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
    
    • 结论

    响应链由UIWindow_sendTouchesForEvent方法调起。
    _sendTouchesForEventsendEvent:方法调起。


    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
    
    • 结论

    依旧会由UIWindow_sendTouchesForEvent:调起响应链。
    但是与普通UIView不同的是UIControltouchesEnded:withEvent时、会结束响应链
    然后直接由UIApplication向其发送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
    
    • 结论

    响应链的开始依旧由UIWindow_sendTouchesForEvent触发。
    但是在sendEvent时、进行了手势判断。
    如果成功识别了手势、则取消响应链
    并且向Target发送Action


    结论

    有几个地方可以提一下:

    1. 响应链本身什么都不做
      所以手势以及UIControl的处理本身并不依赖响应链

    2. 响应链必然会触发、只是后期处理不一样。
      如果是手势、则被取消。如果是UIControl则被结束。

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

    这个光看堆栈应该是看不出来的。
    但结合一些结论、可以试着猜测:

    1. 目标View上有手势
      手势优先识别
    2. 目标View上没有手势但是归属系统UIControl
      UIControl优先处理
    3. 目标View就是个普通View(或者自定义的Control)
      下层手势优先识别、如果没有手势/识别失败则交给响应链
    • 如何将事件发送给指定的View/手势

    全部依赖UITouch的属性

    1. Window
    @property(nullable,nonatomic,readonly,strong) UIWindow *window;
    
    1. 响应链/UIControl
    @property(nullable,nonatomic,readonly,strong) UIView *view;
    
    1. 手势
    @property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
    

    最后

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

    相关文章

      网友评论

      • MemoryReload:写的挺好的。我之前知道,如果视图加了手势,手势先处理,所以,View的touch方法会被cancle。但是,我发现,如果是control的话,control会优先响应,手势根本没有屌用。我只是知道这个现象和经验,但是,没有太深究其中的原理。看完你的帖子,我觉得,跟我想的差不多。然后也理解了那个Guesture的delaysTouchesBegan属性的用处。:+1:
        MemoryReload:@kirito_song :clap::+1:
        kirito_song:@MemoryReload 谢谢。了解一下原理有时候也会很有用处:yum:

      本文标题:iOS基础补完计划--透过堆栈看事件响应机制

      本文链接:https://www.haomeiwen.com/subject/rgdnwftx.html