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

問題Fix:

結合RunLoop和實際堆棧信息解釋點擊事件的傳播(與99%的人認爲的過程不一樣)。最終結果在最後的堆棧信息圖和手繪的事件完整傳遞圖中。設計模式

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

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

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

首先,須要具有RunLoop知識,若是RunLoop快忘光了不建議直接閱讀請點擊:

YY做者ibireme的博客函數

前導圖【Darwin內核架構圖,引自ibireme博客】:

Darwin內核架構圖

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

網上大多數的RunLoop基本上都是抄自這個。確實講的很好,我也讀過這個blog,可是感受根據blog裏對點擊事件的講解理解仍是有點抽象。優化

若是對RunLoop比較瞭解,Continue:

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

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?3d

因而,我決定畫個圖配合堆棧信息講述下點擊事件的全過程,也幫助各位聯繫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.

相關文章
相關標籤/搜索