iOS觸摸事件的流動

原文鏈接:http://qingmo.me/2017/03/04/FlowOfUITouch/
歡迎關注我的微博:http://weibo.com/shellhue

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

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

uitouchflow.png

起始階段

----> 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回調內部,封裝IOHIDEventUIEvent
----> Soucre0回調內部調用UIApplicationsendEvent:方法,將UIEvent傳給UIWindow
----> 平時開發熟悉的觸摸事件響應鏈從這開始了
----> 通過遞歸調用UIView層級的hitTest(_:with:),結合point(inside:with:)找到UIEvent中每一個UITouch所屬的UIView(其實是想找到離觸摸事件點最近的那個UIView)。這個過程是從UIView層級的最頂層往最底層遞歸查詢,但這不是UIResponder響應鏈,事件響應是在UIEvent中每一個UITouch所屬的UIView都確定之后方才開始。

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

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

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

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

calltraceoftouching.png

----> 根據圍繞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

參考文獻:##

  1. 《iOS 事件處理機制與圖像渲染過程》
  2. Event Handling Guide for iOS
  3. IOHIDFamily
  4. SpringBoard
  5. 深入理解RunLoop
  6. Programming iOS 9
  7. GSEvent
  8. 事件傳遞
  9. iOS事件點擊之發生了什么?
  10. IOKit.framework
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 在iOS開發中經常會涉及到觸摸事件。本想自己總結一下,但是遇到了這篇文章,感覺總結的已經很到位,特此轉載。作者:L...
    WQ_UESTC閱讀 6,090評論 4 26
  • 好奇觸摸事件是如何從屏幕轉移到APP內的?困惑于Cell怎么突然不能點擊了?糾結于如何實現這個奇葩響應需求?亦或是...
    Lotheve閱讀 57,866評論 51 603
  • 觸摸事件iOS中的事件:在用戶使用app過程中,會產生各種各樣的事件。iOS中的事件可以分為3大類型 view的觸...
    念念不忘一個丫頭的容閱讀 274評論 0 0
  • 本文介紹了iOS中使用頻率較高的觸摸事件,并闡述了事件產生和傳遞的過程,以及響應者鏈的事件傳遞過程 觸摸事件 簡介...
    擱淺的青蛙閱讀 661評論 0 1
  • 觸摸事件 iOS中的事件 在用戶使用app過程中,會產生各種各樣的事件 iOS中的事件可以分為3大類型:觸摸事件,...
    SoManyDumb閱讀 542評論 0 1