原文鏈接:http://qingmo.me/2017/03/04/FlowOfUITouch/
歡迎關注我的微博:http://weibo.com/shellhue
當指肚輕觸屏幕,整個系統像沉睡的生靈突然被驚醒,然后經歷過腥風血雨的一段奇幻旅行,最終又歸于沉寂。
整個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
不接收任何觸摸事件:
- userInteractionEnabled = NO
- hidden = YES
- alpha = 0.0~0.01之間
提示: UIImageView的userInteractionEnabled默認為NO,因此UIImageView以及它的子控件默認是不接收觸摸事件的。
當把斷點打在某個UIViewhitTest(_: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事件流動完畢,整個系統重新進入睡眠等待下一個事件
總結
從手指觸碰到屏幕,UITouch大致經歷三個階段,系統處理階段---->SpringBoard.app處理階段---->前臺App處理階段
,事實上日常開發只需知曉最后一個階段即可,前兩個階段參考資料也不多,更多的還涉及系統底層,這里僅做簡單介紹。
歡迎關注我的微博:http://weibo.com/shellhue