RunLoop
從看蘋果文檔灾前,了解runloop就看到是這個(gè)圖:
這個(gè)圖只是說runloop利用內(nèi)核mach的通信示意圖 容握,其只是告訴我們r(jià)unloop接受消息刷喜,但是消息到底是什么(mach port消息體)妥泉,處理后怎么發(fā)哪尼吸奴,更不涉及runLoop內(nèi)部的東西共虑,所以不要被此圖迷惑了愧怜。此貌似只能初步直觀的告訴我們r(jià)unloop是一個(gè)循環(huán)。
既然說到這里妈拌,我們在這腦補(bǔ)點(diǎn)這方面的東西拥坛,傳進(jìn)出runLoop處理的完整過程蓬蝶。
首先我們先看IOS/OSX系統(tǒng)的系統(tǒng)架構(gòu)
劃分為4個(gè)層次:
1.應(yīng)用層包括用戶能接觸到的圖形應(yīng)用,例如 Spotlight猜惋、Aqua丸氛、SpringBoard 等。
2.應(yīng)用框架層即開發(fā)人員接觸到的 Cocoa 等框架著摔。
3.核心框架層包括各種核心框架缓窜、OpenGL 等內(nèi)容。
4.Darwin 即操作系統(tǒng)的核心谍咆,包括系統(tǒng)內(nèi)核禾锤、驅(qū)動(dòng)、Shell 等內(nèi)容摹察,這一層是開源的时肿,其所有源碼都可以在opensource.apple.com里找到。
在Darwin這個(gè)核心的架構(gòu):
硬件層上面的三個(gè)組成部分:Mach港粱、BSD螃成、IOKit (還包括一些上面沒標(biāo)注的內(nèi)容),共同組成了 XNU 內(nèi)核
BSD 層可以看作圍繞 Mach 層的一個(gè)外環(huán)查坪,其提供了諸如進(jìn)程管理寸宏、文件系統(tǒng)和網(wǎng)絡(luò)等功能。
Mach 本身提供的 API 非常有限偿曙,而且蘋果也不鼓勵(lì)使用 Mach 的 API氮凝,但是這些API非常基礎(chǔ)望忆,如果沒有這些API的話罩阵,其他任何工作都無法實(shí)施。在 Mach 中启摄,所有的東西都是通過自己的對象實(shí)現(xiàn)的稿壁,進(jìn)程、線程和虛擬內(nèi)存都被稱為"對象"歉备。和其他架構(gòu)不同傅是, Mach 的對象間不能直接調(diào)用,只能通過消息傳遞的方式實(shí)現(xiàn)對象間的通信蕾羊。"消息"是 Mach 中最基礎(chǔ)的概念喧笔,消息在兩個(gè)端口 (port) 之間傳遞,這就是 Mach 的 IPC (進(jìn)程間通信) 的核心龟再。
mach發(fā)送和接受消息是通過同一個(gè) mach_msg() 進(jìn)行的书闸。
說這么多mach是什么意思尼?
RunLoop 的核心就是一個(gè) mach_msg() (文章尾部的runloop源代碼利凑,來說明此點(diǎn))浆劲,RunLoop 調(diào)用這個(gè)函數(shù)去接收消息嫌术,如果沒有別人發(fā)送 port 消息過來,內(nèi)核會將線程置于等待狀態(tài)梳侨。例如你在模擬器里跑起一個(gè) iOS 的 App蛉威,然后在 App 靜止時(shí)點(diǎn)擊暫停日丹,你會看到主線程調(diào)用棧是停留在 mach_msg_trap() 這個(gè)地方走哺。(至于Port消息結(jié)構(gòu)體什么樣,請查找源代碼)
俯瞰后runloop后哲虾,咱們平視r(shí)unloop內(nèi)部:
在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
這里特別說下CFRunLoopModeRef類 這個(gè)類并沒有對外暴漏丙躏,在runloop源代碼里,通過 CFRunLoopRef 的接口進(jìn)行了封裝束凑,如圖:
一個(gè)RunLoop包含有N個(gè)Mode 每個(gè)Mode又包含N個(gè)Source/Timer/Observer(比如你在主線程runloop可以建立無數(shù)個(gè)timer晒旅,observer,以及系統(tǒng)自己無數(shù)輸入源點(diǎn))汪诉。
每次調(diào)用 RunLoop 的主函數(shù)時(shí)废恋,只能指定其中一個(gè) Mode,這個(gè)Mode被稱作 CurrentMode扒寄。如果需要切換 Mode鱼鼓,只能退出 Loop,再重新指定一個(gè) Mode 進(jìn)入该编。這樣做主要是為了分隔開不同組的 Source/Timer/Observer迄本,讓其互不影響。(這句話對實(shí)戰(zhàn)可是有意義的课竣,當(dāng)scroolview豎直滑動(dòng)過程嘉赎,如和保證其上有timer橫行滾動(dòng)焦點(diǎn)圖尼?)
至于CFRunLoopSourceRef于樟,CFRunLoopTimerRef公条,CFRunLoopObserverRef,這個(gè)不用多說了吧迂曲。
Source/Timer/Observer 被統(tǒng)稱為 mode item赃份,一個(gè) item 可以被同時(shí)加入多個(gè) mode。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會有效果的奢米。如果一個(gè) mode 中一個(gè) item 都沒有抓韩,則 RunLoop 會直接退出,不進(jìn)入循環(huán)鬓长。
一個(gè) Mode 可以將自己標(biāo)記為"Common"屬性(通過將其 ModeName 添加到 RunLoop 的 "commonModes" 中)谒拴。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時(shí),RunLoop 都會自動(dòng)將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標(biāo)記的所有Mode里涉波。
應(yīng)用場景舉例:主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode英上。這兩個(gè) Mode 都已經(jīng)被標(biāo)記為"Common"屬性炭序。DefaultMode 是 App 平時(shí)所處的狀態(tài),TrackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)苍日。當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí)惭聂,Timer 會得到重復(fù)回調(diào),但此時(shí)滑動(dòng)一個(gè)TableView時(shí)相恃,RunLoop 會將 mode 切換為 TrackingRunLoopMode辜纲,這時(shí) Timer 就不會被回調(diào),并且也不會影響到滑動(dòng)操作拦耐。
RunLoop的內(nèi)部邏輯
這張圖可比第一張圖可能才是我們深入runloop的第一步耕腾,咱們分析下
1.App 啟動(dòng)后 RunLoop 的狀態(tài):
系統(tǒng)默認(rèn)注冊了5個(gè)Mode:
1. kCFRunLoopDefaultMode: App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的杀糯。
2. UITrackingRunLoopMode: 界面跟蹤 Mode扫俺,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響固翰。
3. UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode狼纬,啟動(dòng)完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode骂际,通常用不到疗琉。
5: kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode,沒有實(shí)際作用方援。
你可以在這里看到更多的蘋果內(nèi)部的 Mode没炒,但那些 Mode 在開發(fā)中就很難遇到了。
當(dāng) RunLoop 進(jìn)行回調(diào)時(shí)犯戏,一般都是通過一個(gè)很長的函數(shù)調(diào)用出去 (call out), 當(dāng)你在你的代碼中下斷點(diǎn)調(diào)試時(shí)送火,通常能在調(diào)用棧上看到這些函數(shù)
1.static?void?__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();//observe
2.static?void?__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();//block
3.static?void?__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();//main_dispatch
4.static?void?__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();//timer
5.static?void?__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();//source0事件
6.static?void?__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();//source1事件
1.Observer事件,runloop中狀態(tài)變化時(shí)進(jìn)行通知先匪。(微信卡頓監(jiān)控就是利用這個(gè)事件通知來記錄下最近一次main runloop活動(dòng)時(shí)間种吸,在另一個(gè)check線程中用定時(shí)器檢測當(dāng)前時(shí)間距離最后一次活動(dòng)時(shí)間過久來判斷在主線程中的處理邏輯耗時(shí)和卡主線程)。這里還需要特別注意呀非,CAAnimation是由RunloopObserver觸發(fā)回調(diào)來重繪坚俗,接下來會講到。
2.Block事件岸裙,非延遲的NSObject PerformSelector立即調(diào)用猖败,dispatch_after立即調(diào)用,block回調(diào)降允。
3.Main_Dispatch_Queue事件:GCD中dispatch到main queue的block會被dispatch到main loop執(zhí)行恩闻。
4.Timer事件:延遲的NSObject PerformSelector,延遲的dispatch_after剧董,timer事件幢尚。
5.Source0事件:處理如UIEvent破停,CFSocket這類事件。需要手動(dòng)觸發(fā)尉剩。觸摸事件其實(shí)是Source1接收系統(tǒng)事件后在回調(diào) __IOHIDEventSystemClientQueueCallback() 內(nèi)觸發(fā)的 Source0真慢,Source0 再觸發(fā)的 _UIApplicationHandleEventQueue()。source0一定是要喚醒runloop及時(shí)響應(yīng)并執(zhí)行的理茎,如果runloop此時(shí)在休眠等待系統(tǒng)的 mach_msg事件黑界,那么就會通過source1來喚醒runloop執(zhí)行。
6.Source1事件:處理系統(tǒng)內(nèi)核的mach_msg事件功蜓。(推測CADisplayLink也是這里觸發(fā))园爷。
關(guān)于以前的框架 你會發(fā)現(xiàn)main函數(shù)里有@autoreleasePool{}
AutoreleasePool
App啟動(dòng)后宠蚂,蘋果在主線程 RunLoop 里注冊了兩個(gè) Observer式撼,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)求厕,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池著隆。其 order 是-2147483647,優(yōu)先級最高呀癣,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前美浦。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來釋放自動(dòng)釋放池项栏。這個(gè) Observer 的 order 是 2147483647浦辨,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后沼沈。
在主線程執(zhí)行的代碼流酬,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的列另。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著芽腾,所以不會出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建 Pool 了页衙。
綜合多篇文章整理所有
參考:深入理解RunLoop
蘋果runLoop文檔