在很多地方忿墅,都能看到這個(gè)概念融求,卻不知道到底是個(gè)什么玩意咬像,網(wǎng)上寫的也很抽象,理解起來(lái)很吃力生宛,官方文檔官方文檔也只是泛泛而談县昂。
那么,什么是RunLoop?
蘋果在取名方面我覺(jué)得非常不錯(cuò)陷舅,runloop就像字面上的意思一樣運(yùn)行循環(huán)倒彰,俗稱“兜圈圈”。也就是死循環(huán)莱睁,在這個(gè)循環(huán)中待讳,會(huì)去處理一系列的事件。假如說(shuō)這個(gè)循環(huán)停止了仰剿,那么程序就已經(jīng)停止了耙箍。
在創(chuàng)建一個(gè)工程,都會(huì)有個(gè)main函數(shù)酥馍,在這個(gè)函數(shù)里就是去運(yùn)行程序的時(shí)候創(chuàng)建了個(gè)主線程的RunLoop,也是保持程序持續(xù)運(yùn)行的關(guān)鍵辩昆。
如果改成return 0;那么程序?qū)⒉粫?huì)持續(xù)運(yùn)行,剛運(yùn)行就停止了旨袒。也不會(huì)去處理app的各種事件汁针,這也是為什么要去使用RunLoop了术辐,同樣,在其他操作系統(tǒng)上也有這個(gè)概念施无。
請(qǐng)問(wèn)這個(gè)打印會(huì)正常打印出來(lái)么辉词?可以先思考下為什么。
答案是不會(huì)打印的猾骡,因?yàn)樵谏弦痪淙鹛桑统伤姥h(huán),也就是弄了個(gè)runloop兴想,一直卡在上一句代碼幢哨,也就是保持了程序的持續(xù)運(yùn)行,如果說(shuō)能打印嫂便,那么return也能返回值捞镰,那么程序就沒(méi)法持續(xù)保持運(yùn)行,一旦返回值那么意味著結(jié)束毙替。
RunLoop有什么用呢岸售?
1.保持程序的持續(xù)運(yùn)行
2.處理App的各種事件(比如觸摸事件、定時(shí)器事件厂画、Selector事件)
3.節(jié)省CPU資源凸丸,提高程序性能。(該做事的時(shí)候做事袱院,該休息的時(shí)候休息)
RunLoop對(duì)象
在iOS中有2套API去訪問(wèn)和使用RunLoop
Foundation框架
NSRunLoop
Core Foundation框架
CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表RunLoop對(duì)象屎慢,NSRunLoop是基于CFRunLoopRef的一層OC封裝,為此要理解RunLoop內(nèi)部結(jié)構(gòu)坑填,要多研究CFRunLoopRef
NSRrunLoop缺點(diǎn):效率低(因?yàn)槭巧蠈臃庋b)抛人,封裝的不算好弛姜,可提供的方法很少脐瑰。
CFRunLoopRef是開(kāi)源的,開(kāi)源地址CFRunLoopRef開(kāi)源地址
RunLoop和線程
1.每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象
2.主線程的RunLoop已經(jīng)自動(dòng)創(chuàng)建好了廷臼,子線程的RunLoop需要主動(dòng)創(chuàng)建(默認(rèn)關(guān)閉的)
3.RunLoop在第一次獲取時(shí)創(chuàng)建苍在,在線程結(jié)束時(shí)銷毀。
如何獲取RunLoop對(duì)象
Foundation框架
[NSRunLoop currentRunLoop];//獲取當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop];//獲取主線程的RunLoop對(duì)象
Core Foundation
CFRunLoopGetCurrent();//獲取當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain();//獲得主線程的RunLoop對(duì)象
RunLoop相關(guān)類
Core Foundation中關(guān)于RunLoop有5個(gè)類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef ?
CFRunLoopObserverRef
RunLoop中有多個(gè)Mode荠商,可以在多個(gè)Mode中來(lái)回切換寂恬,Mode是RunLoop重要的組成部分,沒(méi)有Mode,RunLoop就跑不起來(lái).
在一個(gè)Mode里莱没,不是必須Source,Timer,Observer三個(gè)必須有值初肉,有一個(gè)有值,RunLoop也是可以跑起來(lái)的饰躲。
CFRunLoopModeRef
1.CFRunLoopModeRef代表RunLoop的運(yùn)行模式
2.一個(gè)RunLoop包含若干個(gè)Mode牙咏,每個(gè)Mode又包含若干個(gè)Source/Timer/Observer
3.每次RunLoop啟動(dòng)時(shí)臼隔,只能指定其中一個(gè)Mode,這個(gè)Mode又叫做CurrentMode
4.如果需要切換Mode妄壶,只能退出Loop摔握,再重新指定一個(gè)Mode進(jìn)入Loop。這樣做主要是為了分隔不同組的Source/Timer/Observer丁寄,讓其互不影響氨淌。
系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
kCFRunLoopDefaultMode:app默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode:界面跟蹤Mode伊磺,用于Scrollview追蹤觸摸滑動(dòng)盛正,保證界面滑動(dòng)不受其他Mode影響
UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后就不再使用
GSEventReceiveRunLoopMode:接受系統(tǒng)時(shí)間的內(nèi)部Mode奢浑,通常用不到
kCFRunLoopCommonModes:這是一個(gè)占位用的Mode蛮艰,不是一種真正的Mode,即可能kCFRunLoopDefaultMode雀彼,也可能是UITrackingRunLoopMode
例子:
當(dāng)不去滑動(dòng)TextView時(shí)壤蚜,當(dāng)前RunLoopMode是NSDefaultMode,所以定時(shí)器會(huì)正常運(yùn)作徊哑;當(dāng)去滑動(dòng)TextView時(shí)袜刷,當(dāng)前RunLoopMode切換到UITrackingRunLoopMode,定時(shí)器又要在NSDefaultMode模式下才能運(yùn)行莺丑,所以定時(shí)器失效著蟹;當(dāng)不再滑動(dòng),又切換到NSDefaultMode梢莽,定時(shí)器又可以正常運(yùn)作萧豆。
我們知道了是因?yàn)镽unLoop模式導(dǎo)致了,所以昏名,將定時(shí)器在的模式更改就行了涮雷。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
Mode的一個(gè)組成部分,CFRunLoopSourceRef被稱為事件源(輸入源)轻局。RunLoopSource包括2種source:分別為Source0和Source1洪鸭。
Source0:非基于port的
只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件仑扑。使用時(shí)览爵,你需要先調(diào)用。(通俗點(diǎn)說(shuō):1.自定義的事件镇饮,自己寫的事件蜓竹;2.PerformSelecter事件)
Source1:基于port的
包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線程(簡(jiǎn)單地說(shuō):系統(tǒng)做的事件)
(補(bǔ)充:port就是端口內(nèi)核的意思俱济,也可以理解為cpu)
上圖是我在storyboard拖的button事件方法司蔬,通過(guò)設(shè)置斷點(diǎn),可以看到線程棧的情況姨蝴,很清楚的看到Source0俊啼,也可以說(shuō)明,事件源是由自定義寫的左医。
CFRunLoopTimerRef
基于時(shí)間的觸發(fā)器(說(shuō)白了授帕,基本就是NSTimer)
CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變
可以監(jiān)聽(tīng)的時(shí)間點(diǎn)有下面幾個(gè)
/*Run Loop Observer Activities*/
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){
? ? ? ?kCFRunLoopEntry = (1UL << 0), ? ? ? ?//即將進(jìn)入RunLoop
? ? ? ?kCFRunLoopBeforeTimers = (1UL << 1), ?//即將處理Timer
? ? ? ?kCFRunLoopBeforeSources = (1UL << 2), //即將處理Source
? ? ? ?kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
? ? ? ?kCFRunLoopAfterWaiting = (1UL << 8), ?//剛從休眠中喚醒
? ? ? ?kCFRunLoopExit = (1UL << 7), ? ? ? ? //即將退出RunLoop
? ? ? ?kCFRunLoopAllActivities = 0x0FFFFFFFU ? //監(jiān)聽(tīng)RunLoop所有情況
}
如何去創(chuàng)建一個(gè)ObserverRef
列1:我們來(lái)看看應(yīng)用從啟動(dòng)到界面顯示浮梢,runloop狀態(tài)是如何變化的跛十。
可以看到,從最開(kāi)始的1(即將進(jìn)入RunLoop)到最后的32(即將進(jìn)入休眠)一個(gè)過(guò)程秕硝。
列1:我們來(lái)監(jiān)聽(tīng)下一個(gè)按鈕點(diǎn)擊后芥映,RunLoop狀態(tài)的變化
可以看到,從最開(kāi)始的64(剛從休眠喚醒)到最后的32(即將進(jìn)入休眠)远豺。
為此奈偏,我們可以利用runloop的狀態(tài)改變來(lái)處理一些不好處理的事件,具體需要具體情況而定躯护,這樣runloop就可以很好的結(jié)合起來(lái)使用了惊来。
知道了RunLoop邏輯處理和RunLoop結(jié)構(gòu)后,我們來(lái)看看官方提供的RunLoop處理邏輯圖就很好理解了棺滞。
左圖的就是RunLoop裁蚁,Runloop是跟線程一一綁定的,所以是個(gè)Thread继准。然后Runloop一直跑圈枉证,在跑圈的過(guò)程中,右圖會(huì)有Sources傳入進(jìn)行事件操作移必,如Timer室谚,基于Port事件,自定義事件避凝,performSelector事件舞萄。
RunLoop處理邏輯過(guò)程
總結(jié):
? ? 1.主線程的RunLoop默認(rèn)開(kāi)啟
? ? 2.子線程的RunLoop默認(rèn)關(guān)閉眨补,需要手動(dòng)開(kāi)啟
? ? 3.RunLoop開(kāi)啟的時(shí)候要包含Mode管削,必須要某個(gè)確定的模式下運(yùn)行,同時(shí)在一個(gè)模式下運(yùn)行
? ? 4.observer可以監(jiān)聽(tīng)RunLoop所有狀態(tài)事件撑螺,可以做到一些別人做不到的事含思。