問題Fix:
結合RunLoop
和實際堆疊資訊解釋點選事件的傳播(與99%的人認為的過程不同)。最終結果在最後的堆疊資訊圖
和手繪的事件完整傳遞圖
中。
事情經過:被某大佬問了個問題:描述下Button的點選事件
像我這種小白開發一般都是從事件的傳遞來講的:就是UIApplication找尋最優響應者的過程(這裡就不贅述了)。
好吧,直接給出總結的答案:
首先,需要具備RunLoop知識,如果RunLoop快忘光了不建議直接閱讀請點選:
前導圖【Darwin核心架構圖,引自ibireme部落格】:
簡短描述: 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.