iOS基礎補完計劃--透過堆疊看事件響應機制

ofollow,noindex">目錄
- 前言
- 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
方法調起。
而 _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
-
結論
依舊會由 UIWindow
的 _sendTouchesForEvent:
調起響應鏈。
但是與普通 UIView
不同的是 UIControl
在 touchesEnded: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
。
結論

有幾個地方可以提一下:
-
響應鏈本身什麼都不做
所以手勢以及UIControl的處理本身並不依賴響應鏈
-
響應鏈必然會觸發、只是後期處理不一樣。
如果是手勢、則被取消。如果是UIControl則被結束。
-
關於事件處理的優先順序。
這個光看堆疊應該是看不出來的。
但結合一些結論、可以試著猜測:
- 目標View上有手勢
手勢優先識別 - 目標View上沒有手勢但是歸屬系統UIControl
UIControl優先處理 - 目標View就是個普通View(或者自定義的Control)
下層手勢優先識別、如果沒有手勢/識別失敗則交給響應鏈
-
如何將事件傳送給指定的View/手勢
全部依賴UITouch的屬性
- Window
@property(nullable,nonatomic,readonly,strong) UIWindow *window;
- 響應鏈/UIControl
@property(nullable,nonatomic,readonly,strong) UIView *view;
- 手勢
@property(nullable,nonatomic,readonly,copy)NSArray <UIGestureRecognizer *> *gestureRecognizers NS_AVAILABLE_IOS(3_2);
最後
本文主要是自己的學習與總結。如果文記憶體在紕漏、萬望留言斧正。如果願意補充以及不吝賜教小弟會更加感激。