手繪圖解:一次點選事件的面試題(基於RunLoop)

Nil_Lu發表於2019-03-02

問題Fix:

結合RunLoop和實際堆疊資訊解釋點選事件的傳播(與99%的人認為的過程不同)。最終結果在最後的堆疊資訊圖和手繪的事件完整傳遞圖中。

事情經過:被某大佬問了個問題:描述下Button的點選事件

像我這種小白開發一般都是從事件的傳遞來講的:就是UIApplication找尋最優響應者的過程(這裡就不贅述了)。

好吧,直接給出總結的答案:

首先,需要具備RunLoop知識,如果RunLoop快忘光了不建議直接閱讀請點選:

YY作者ibireme的部落格

前導圖【Darwin核心架構圖,引自ibireme部落格】:

Darwin核心架構圖

簡短描述: IOKit負責響應硬體事件,Darwin核心發出Source1 <mach_port> 訊息。

網上大多數的RunLoop基本上都是抄自這個。確實講的很好,我也讀過這個blog,但是感覺根據blog裡對點選事件的講解理解還是有點抽象。

如果對RunLoop比較瞭解,Continue:

ibreime的原文中對Source0和Source1的描述如下:

1,Source0 只包含了一個回撥(函式指標),它並不能主動觸發事件。使用時,你需要先呼叫 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然後手動呼叫 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop。

2,Source1 包含了一個 mach_port 和一個回撥(函式指標),被用於通過核心和其他執行緒相互傳送訊息。這種 Source 能主動喚醒 RunLoop 的執行緒。

下面結合一個最簡單的點選事件分析下:

事件描述:點選螢幕,在- touchesBegan處打斷點
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
複製程式碼

預想過程:

Source1和Source0都可以喚醒RunLoop,所以應該是RunLoop收到Source1直接封裝成UIEvent再分發,但是實際發現,RunLoop的堆疊呼叫資訊中並沒有Source1的身影,只有Source0?

於是,我決定畫個圖配合堆疊資訊講述下點選事件的全過程,也幫助各位聯絡RunLoop的知識。

解釋:

堆疊呼叫資訊:

堆疊呼叫資訊

按照RunLoop的說法,這裡應該是Source1喚醒RunLoop才對,但是堆疊資訊中卻沒有收到Source1資訊,只有Source0(UIEvent屬於Source0),

結合ibireme部落格對事件響應的描述:

當一個硬體事件(觸控/鎖屏/搖晃等)發生後,首先由 IOKit.framework 生成一個 IOHIDEvent 事件並由 SpringBoard 接收。這個過程的詳細情況可以參考這裡。SpringBoard 只接收按鍵(鎖屏/靜音等),觸控,加速,接近感測器等幾種 Event,隨後用 mach port 轉發給需要的App程式。隨後蘋果註冊的那個 Source1 就會觸發回撥,並呼叫 _UIApplicationHandleEventQueue() 進行應用內部的分發。 _UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理幷包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理螢幕旋轉/傳送給 UIWindow 等。通常事件比如 UIButton 點選、touchesBegin/Move/End/Cancel 事件都是在這個回撥中完成的。

詳細的過程請看圖:(水果機拍的,不要吐槽)

點選事件的完整過程:

點選事件的完整過程

至此一次Button的點選事件結束,雖然RunLoop天天說,但是在實際開發中卻不常用,但是,其實RunLoop就跟設計模式一樣,無處不在,知識串起來之後其實對很多Bug的fix和優化會有很大啟發。

End.

相關文章