iOS觸摸事件的流動

BarbOvens 7年前發布 | 8K 次閱讀 iOS開發 移動開發

當指肚輕觸屏幕,整個系統像沉睡的生靈突然被驚醒,然后經歷過腥風血雨的一段奇幻旅行,最終又歸于沉寂。

整個iOS觸摸事件從產生到寂滅大致如下圖:

起始階段

—-> cpu處于睡眠狀態,等待事件發生

—-> 手指觸摸屏幕

系統響應階段

—-> 屏幕硬件感應到輸入,并將感應到的事件傳遞給輸入輸出驅動IOKit

—-> IOKit.framework封裝整個觸摸事件為IOHIDEvent對象

—-> IOKit.framework通過IPC將事件轉發給SpringBoard.app

以上是系統層的響應。系統感應到外界的輸入,并將相應的輸入封裝成比較概括的 IOHIDEvent 對象,然后UIKit通過IOHIDEvent的類型,判斷出相應事件應該由 SpringBoard .app 處理,直接通過mach port(IPC進程間通信)轉發給SpringBoard.app。

SpringBoard.app就是iOS的系統桌面,當觸摸事件發生時,也只有負責管理桌面的SpringBoard.app才知道如何正確的響應。因為觸摸發生時,有可能用戶正在桌面翻頁找App,也有可能正處于在微信中刷朋友圈。

桌面響應階段

—-> SpringBoard.app主線程Runloop收到 IOKit.framework 轉發來的消息蘇醒,并觸發對應Mach Port的Source1回調 __IOHIDEventSystemClientQueueCallback() 。

—-> 如果SpringBoard.app監測到有App在前臺(記為xxxx.app),SpringBoard.app通過mach port(IPC進程間通信)轉發給xxxx.app,如果SpringBoard.app監測到監測無前臺App,則SpringBoard.app進入App內部響應階段的第二段,記觸發Source0回調。

App內部響應階段

—-> 前臺App主線程Runloop收到SpringBoard.app轉發來的消息蘇醒,并觸發對應Mach Port的Source1回調 __IOHIDEventSystemClientQueueCallback() 。

—-> Source1回調內部觸發Source0回調 __UIApplicationHandleEventQueue()
—-> Soucre0回調內部,封裝 IOHIDEvent 為 UIEvent
—-> Soucre0回調內部調用 UIApplication 的 sendEvent: 方法,將 UIEvent 傳給 UIWindow

—-> 平時開發熟悉的觸摸事件響應鏈從這開始了

—-> 通過遞歸調用UIView層級的 hitTest(_:with:) ,結合 point(inside:with:) 找到 UIEvent 中每一個 UITouch 所屬的 UIView (其實是想找到離觸摸事件點最近的那個 UIView )。這個過程是從 UIView 層級的最頂層往最底層遞歸查詢,但這不是 UIResponder 響應鏈,事件響應是在 UIEvent 中每一個 UITouch 所屬的 UIView 都確定之后方才開始。

但需要注意,以下三種情況 UIView 的 hitTest(_:with:) 不會被調用,也導致其子 UIView 的 hitTest(_:with:) 不會被調用,而之后響應事件是下向上傳遞的,這直接導致以下三種情況的 UIView 及其子 UIView 不接收任何觸摸事件:

  1. userInteractionEnabled = NO
  2. hidden = YES
  3. alpha = 0.0~0.01之間

提示: UIImageView的userInteractionEnabled默認為NO,因此UIImageView以及它的子控件默認是不接收觸摸事件的。

當把斷點打在某個UIView hitTest(_:with:) 中時,對應的調用堆棧如下:

—-> 根據圍繞 UITouch 所屬的 UIView 及其祖先 UIView 的gesture recognizers,來確定一個UITouch的gestureRecognizers

—-> UITouch所屬的UIView和gestureRecognizers收到此UITouch和相應的UIEvent,并按照UITouch所處的狀態調用四大UITouch方法 touchesBegan(_:with:) touchesMoved(_:with:) touchesEnded(_:with:) touchesCancelled(_:with:) 中的一個。(事件響應開始)

—-> 對于UIView收到的UITouches事件(四大UITouch事件都是如此),則會按照UIResponder響應鏈一直往上傳遞,直到某個UIResponder因為主動響應觸摸事件,切斷了響應鏈(即不調用下一個UIResponder的響應方法),如果一直沒有UIResponder做響應處理,則這些UITouches到達最后的響應者即UIApplication后,就被吃掉了,消失了。

—-> 如果在事件響應過程中,有UIGestureRecognizer成功識別,則此UIGestureRecognizer將獨自占有所需要的UITouches,這些UITouches所屬的UIView及其他的UIGestureRecognizer的 touchesCancelled(_:with:) 方法將調用(如果在手勢的代理中設置可以同時識別兩個手勢,則允許同時識別的手勢均可以收到所需要的UITouches事件)。但與識別成功的UIGestureRecognizer無關的UITouches則會繼續按照上述傳遞邏輯傳遞。也即允許兩個手勢同時識別,只要所占有的UITouches不相同。

—-> 如果UIGestureRecognizer識別成功,則調用相應的action,處理對應的邏輯。如果某個UIResponder主動響應了觸摸事件,則根據其本身的響應邏輯處理對應的業務,UIControl都是主動響應并切斷UITouch的向上傳遞的。

—-> UITouches事件流動完畢,整個系統重新進入睡眠等待下一個事件

 

來自:http://shellhue.github.io/2017/03/04/FlowOfUITouch/

 

 本文由用戶 BarbOvens 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!