原文鏈接:http://qingmo.me/2017/03/04/FlowOfUITouch/
歡迎關(guān)注我的微博:http://weibo.com/shellhue
當(dāng)指肚輕觸屏幕,整個(gè)系統(tǒng)像沉睡的生靈突然被驚醒,然后經(jīng)歷過腥風(fēng)血雨的一段奇幻旅行绒疗,最終又歸于沉寂宏蛉。
整個(gè)iOS觸摸事件從產(chǎn)生到寂滅大致如下圖:
起始階段
----> cpu處于睡眠狀態(tài)恼除,等待事件發(fā)生
----> 手指觸摸屏幕
系統(tǒng)響應(yīng)階段
----> 屏幕硬件感應(yīng)到輸入,并將感應(yīng)到的事件傳遞給輸入輸出驅(qū)動(dòng)IOKit
----> IOKit.framework封裝整個(gè)觸摸事件為IOHIDEvent對象
----> IOKit.framework通過IPC將事件轉(zhuǎn)發(fā)給SpringBoard.app
以上是系統(tǒng)層的響應(yīng)曼氛。系統(tǒng)感應(yīng)到外界的輸入豁辉,并將相應(yīng)的輸入封裝成比較概括的IOHIDEvent對象,然后UIKit通過IOHIDEvent的類型搪锣,判斷出相應(yīng)事件應(yīng)該由SpringBoard .app處理秋忙,直接通過mach port(IPC進(jìn)程間通信)轉(zhuǎn)發(fā)給SpringBoard.app。
SpringBoard.app就是iOS的系統(tǒng)桌面构舟,當(dāng)觸摸事件發(fā)生時(shí)灰追,也只有負(fù)責(zé)管理桌面的SpringBoard.app才知道如何正確的響應(yīng)堵幽。因?yàn)橛|摸發(fā)生時(shí),有可能用戶正在桌面翻頁找App弹澎,也有可能正處于在微信中刷朋友圈朴下。
桌面響應(yīng)階段
----> SpringBoard.app主線程Runloop收到IOKit.framework轉(zhuǎn)發(fā)來的消息蘇醒,并觸發(fā)對應(yīng)Mach Port的Source1回調(diào)__IOHIDEventSystemClientQueueCallback()
苦蒿。
----> 如果SpringBoard.app監(jiān)測到有App在前臺(tái)(記為xxxx.app)殴胧,SpringBoard.app通過mach port(IPC進(jìn)程間通信)轉(zhuǎn)發(fā)給xxxx.app,如果SpringBoard.app監(jiān)測到監(jiān)測無前臺(tái)App佩迟,則SpringBoard.app進(jìn)入App內(nèi)部響應(yīng)階段的第二段团滥,記觸發(fā)Source0回調(diào)。
App內(nèi)部響應(yīng)階段
----> 前臺(tái)App主線程Runloop收到SpringBoard.app轉(zhuǎn)發(fā)來的消息蘇醒报强,并觸發(fā)對應(yīng)Mach Port的Source1回調(diào)__IOHIDEventSystemClientQueueCallback()
灸姊。
----> Source1回調(diào)內(nèi)部觸發(fā)Source0回調(diào)__UIApplicationHandleEventQueue()
----> Soucre0回調(diào)內(nèi)部,封裝IOHIDEvent
為UIEvent
----> Soucre0回調(diào)內(nèi)部調(diào)用UIApplication
的sendEvent:
方法秉溉,將UIEvent
傳給UIWindow
----> 平時(shí)開發(fā)熟悉的觸摸事件響應(yīng)鏈從這開始了
----> 通過遞歸調(diào)用UIView層級(jí)的hitTest(_:with:)
力惯,結(jié)合point(inside:with:)
找到UIEvent
中每一個(gè)UITouch
所屬的UIView
(其實(shí)是想找到離觸摸事件點(diǎn)最近的那個(gè)UIView
)。這個(gè)過程是從UIView
層級(jí)的最頂層往最底層遞歸查詢召嘶,但這不是UIResponder
響應(yīng)鏈父晶,事件響應(yīng)是在UIEvent
中每一個(gè)UITouch
所屬的UIView
都確定之后方才開始。
但需要注意弄跌,以下三種情況UIView
的hitTest(_:with:)
不會(huì)被調(diào)用甲喝,也導(dǎo)致其子UIView
的hitTest(_:with:)
不會(huì)被調(diào)用,而之后響應(yīng)事件是下向上傳遞的铛只,這直接導(dǎo)致以下三種情況的UIView
及其子UIView
不接收任何觸摸事件:
- userInteractionEnabled = NO
- hidden = YES
- alpha = 0.0~0.01之間
提示: UIImageView的userInteractionEnabled默認(rèn)為NO,因此UIImageView以及它的子控件默認(rèn)是不接收觸摸事件的俺猿。
當(dāng)把斷點(diǎn)打在某個(gè)UIViewhitTest(_:with:)
中時(shí),對應(yīng)的調(diào)用堆棧如下:
----> 根據(jù)圍繞UITouch
所屬的UIView
及其祖先UIView
的gesture recognizers格仲,來確定一個(gè)UITouch的gestureRecognizers
----> UITouch所屬的UIView和gestureRecognizers收到此UITouch和相應(yīng)的UIEvent,并按照UITouch所處的狀態(tài)調(diào)用四大UITouch方法touchesBegan(_:with:) touchesMoved(_:with:) touchesEnded(_:with:) touchesCancelled(_:with:)
中的一個(gè)诵冒。(事件響應(yīng)開始)
----> 對于UIView收到的UITouches事件(四大UITouch事件都是如此)凯肋,則會(huì)按照UIResponder響應(yīng)鏈一直往上傳遞,直到某個(gè)UIResponder因?yàn)橹鲃?dòng)響應(yīng)觸摸事件汽馋,切斷了響應(yīng)鏈(即不調(diào)用下一個(gè)UIResponder的響應(yīng)方法)侮东,如果一直沒有UIResponder做響應(yīng)處理,則這些UITouches到達(dá)最后的響應(yīng)者即UIApplication后豹芯,就被吃掉了悄雅,消失了。
----> 如果在事件響應(yīng)過程中铁蹈,有UIGestureRecognizer成功識(shí)別宽闲,則此UIGestureRecognizer將獨(dú)自占有所需要的UITouches,這些UITouches所屬的UIView及其他的UIGestureRecognizer的touchesCancelled(_:with:)
方法將調(diào)用(如果在手勢的代理中設(shè)置可以同時(shí)識(shí)別兩個(gè)手勢,則允許同時(shí)識(shí)別的手勢均可以收到所需要的UITouches事件)容诬。但與識(shí)別成功的UIGestureRecognizer無關(guān)的UITouches則會(huì)繼續(xù)按照上述傳遞邏輯傳遞娩梨。也即允許兩個(gè)手勢同時(shí)識(shí)別,只要所占有的UITouches不相同览徒。
----> 如果UIGestureRecognizer識(shí)別成功狈定,則調(diào)用相應(yīng)的action,處理對應(yīng)的邏輯习蓬。如果某個(gè)UIResponder主動(dòng)響應(yīng)了觸摸事件纽什,則根據(jù)其本身的響應(yīng)邏輯處理對應(yīng)的業(yè)務(wù),UIControl都是主動(dòng)響應(yīng)并切斷UITouch的向上傳遞的躲叼。
----> UITouches事件流動(dòng)完畢芦缰,整個(gè)系統(tǒng)重新進(jìn)入睡眠等待下一個(gè)事件
總結(jié)
從手指觸碰到屏幕,UITouch大致經(jīng)歷三個(gè)階段押赊,系統(tǒng)處理階段---->SpringBoard.app處理階段---->前臺(tái)App處理階段
饺藤,事實(shí)上日常開發(fā)只需知曉最后一個(gè)階段即可,前兩個(gè)階段參考資料也不多流礁,更多的還涉及系統(tǒng)底層涕俗,這里僅做簡單介紹。
歡迎關(guān)注我的微博:http://weibo.com/shellhue