在我們平時的開發(fā)過程中會涉及到RunLoop的開發(fā)其實是非常少的,但是RunLoop其實是保證App能夠正常運行的一個非常關(guān)鍵的東西拓劝。
什么是RunLoop熟丸?
我們現(xiàn)在的手機系統(tǒng)都是一個事件驅(qū)動模式的系統(tǒng),當(dāng)用戶觸發(fā)了某個事件很魂,系統(tǒng)會快速的進(jìn)行響應(yīng)處理讶泰。一般的線程都是從開始直接走到結(jié)束的一個直線型的過程咏瑟,結(jié)束后線程就被釋放了。而RunLoop的存在就是為了能保持線程的持續(xù)存在痪署,當(dāng)有需要的時候進(jìn)行激活處理事件码泞,不需要的時候進(jìn)行掛起休眠等待下一次的事件激活。
RunLoop的作用
1狼犯、使程序一直保持運行并能在用戶有輸入操作的時候進(jìn)行處理
2余寥、決定程序在任何應(yīng)該處理哪些事情
3、調(diào)用解耦悯森,消息隊列
4宋舷、節(jié)省CPU時間(在沒有需要處理的消息的時候進(jìn)行掛起休眠)
這里對2、3進(jìn)行解釋一下瓢姻,當(dāng)用戶進(jìn)行操作APP的時候肯定不是僅僅觸發(fā)某一個事件的祝蝠,必定是會觸發(fā)一系列的事件。這時候RunLoop會有個管理的消息隊列來做管理幻碱,先處理哪個再處理哪個绎狭。這時候用戶的消息觸發(fā)與程序的處理其實就解耦分開了,用戶只需要做自己的操作褥傍,剩下的都交給程序自己去做管理處理儡嘶。
RunLoop的機制
在我們運行的APP中打上斷點并進(jìn)行觸發(fā),看看調(diào)用棧
我現(xiàn)在是斷在了一個button的點擊觸發(fā)事件那里恍风。我們看看調(diào)用堆棧里面的調(diào)用順序蹦狂。
最下面的start,main是整個程序的起調(diào)誓篱,然后到了UIKit里面的Main函數(shù),GSEventRunModel這個是物理內(nèi)核對用戶的點擊各種觸發(fā)事件的處理凯楔,然后就調(diào)到了RunLoop這里窜骄,獲取了點擊事件然后進(jìn)行事件消息處理分發(fā),到最上的就是我們熟悉的各種層層函數(shù)的調(diào)用啼辣。
我們這邊看到箭頭指的這個一長串東西啊研,其實是RunLoop的六種起調(diào)狀態(tài),我們的絕大部分都是從這六種狀態(tài)進(jìn)行調(diào)起的鸥拧,但也并不是絕對党远,如果自己創(chuàng)建一個線程去調(diào)用方法,是有可能不從這六個狀態(tài)進(jìn)行調(diào)起方法的
這里面的CAllING_OUT其實就可以看出來作用是進(jìn)行調(diào)出的富弦,主要看的是標(biāo)紅的地方
這邊解釋一下最下面兩個Source0和Source1
Source0:處理APP內(nèi)部事件沟娱,APP自己負(fù)責(zé)管理(觸發(fā))例如UIEvent
Source1:由RunLoop和內(nèi)核管理,Mach port驅(qū)動
Mach port
輕量級的進(jìn)程通訊腕柜,有點類似于網(wǎng)絡(luò)通訊端口济似,比方說手機的定位、網(wǎng)絡(luò)數(shù)據(jù)下載盏缤,都是手機通過調(diào)接口傳入到APP內(nèi)再調(diào)到相應(yīng)的函數(shù)
當(dāng)Xcode點擊暫停按鈕砰蠢,APP就會處于一個掛起的trap狀態(tài)
當(dāng)另一個線程(或者另一個進(jìn)程中的某個線程)向內(nèi)核發(fā)消息,trap狀態(tài)就會被喚醒唉铜,進(jìn)行事件處理
RunLoop的結(jié)構(gòu)
這張是從其他地方扒過來的圖
RunLoop與線程的關(guān)系
在我的理解中台舱,RunLoop是依托于線程的,但是并不是所有線程都會有RunLoop的潭流。線程剛創(chuàng)建時并沒有 RunLoop竞惋,如果你不主動獲取,那它一直都不會有灰嫉。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時拆宛,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)讼撒。
CFRunLoopTimer
NSTimer是對這個的一個上層封裝
除了GCD的timer其他的timer都是基于RunLoop來實現(xiàn)的
CFRunLoopSource
類似于一個協(xié)議Protocol浑厚,當(dāng)符合條件則觸發(fā)
在RunLoop里面自己定義了兩個,一個是Source0一個是Source1
聽說還可以自己定義椿肩,這里沒有仔細(xì)研究
CFRunLoopObserver
向外部報告現(xiàn)在的狀態(tài)
這個與Autorelease Pool的關(guān)系
Autorelease Pool
在App啟動后, 系統(tǒng)會在主線程里先注冊兩個Observer, 回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()
Observer1: 監(jiān)視即將進(jìn)入RunLoop, 在這個回調(diào)內(nèi)會去調(diào)用_objc_autoreleasePoolPush()來創(chuàng)建一個自動釋放池, 它的order是-2147483647, 最高優(yōu)先級, 醬紫就可以保證創(chuàng)建自動釋放池是在其他回調(diào)之前
Observer2: 監(jiān)聽了兩件事
第一: 在即將進(jìn)入休眠(BeforeWaiting)的時候調(diào)用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()
第二: 在即將退出RunLoop(Exit)的時候調(diào)用_objc_autoreleasePoolPop()來釋放自動釋放池
注意: 這個Observer2的order是2147483647, 優(yōu)先級最低, 醬紫就可以保證在處理完所有事情之后再去釋放這個自動釋放池
我們在主線程中執(zhí)行的代碼, 一般都是寫在事件回調(diào), Timer回調(diào)內(nèi), 醬紫回調(diào)就會被RunLoop所創(chuàng)建的自動釋放池(Autorelease Pool)里循環(huán)著, 我們不用去擔(dān)心內(nèi)存泄漏什么之類的, 也不需要去顯示的去創(chuàng)建Pool
Mode
一個 RunLoop 包含若干個 Mode瞻颂,每個 Mode 又包含若干個 Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時郑象,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode茬末。如果需要切換 Mode厂榛,只能退出 Loop盖矫,再重新指定一個 Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer击奶,讓其互不影響辈双。
RunLoop與GCD
RunLoop和GCD其實是協(xié)調(diào)的關(guān)系,當(dāng)GCD觸發(fā)在主線程dispatch_get_main_queue()做事情的時候柜砾,RunLoop做的是幫GCD進(jìn)行調(diào)起
實踐
1湃望、保持線程的長久存活
2、通過切換mode來進(jìn)行不同事件的調(diào)用時機控制