iOS RunLoop

關(guān)于 runloop 面試中經(jīng)常被問到:

講講 RunLoop,項(xiàng)目中有用到嗎付枫?

RunLoop內(nèi)部實(shí)現(xiàn)邏輯抱完?

Runloop和線程的關(guān)系?

timer 與 Runloop 的關(guān)系饵沧?

程序中添加每3秒響應(yīng)一次的NSTimer,當(dāng)拖動tableview時timer可能無法響應(yīng)要怎么解決赌躺?

Runloop 是怎么響應(yīng)用戶操作的狼牺, 具體流程是什么樣的?

說說RunLoop的幾種狀態(tài)礼患?

Runloop的mode作用是什么是钥?

一. RunLoop簡介

運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情缅叠,如果沒有Runloop程序執(zhí)行完畢就會立即退出悄泥,如果有Runloop程序會一直運(yùn)行,并且時時刻刻在等待用戶的輸入操作肤粱。RunLoop可以在需要的時候自己跑起來運(yùn)行弹囚,在沒有操作的時候就停下來休息。充分節(jié)省CPU資源领曼,提高程序性能鸥鹉。

  • 定義
    RunLoop的實(shí)質(zhì)是一個死循環(huán),用于保證程序的持續(xù)運(yùn)行庶骄,只有當(dāng)程序退出的時候才會結(jié)束(由main函數(shù)開啟主線程的RunLoop)

  • 作用
    保持程序的持續(xù)運(yùn)行
    處理App中的各種事件(觸摸毁渗、定時器、Selector事件)
    節(jié)省CPU資源单刁,提高程序性能(該做事做事祝蝠,沒事做休息)

  • 獲取方法
    使用NSRunLoop(面向?qū)ο螅┗蛘逤FRunLoopRef(底層C語言)
    在任何一個Cocoa程序的線程中,都可以通過:
    NSRunLoop *runloop = [NSRunLoopcurrentRunLoop];
    來獲取到當(dāng)前線程的run loop幻碱。

  • 原理
    RunLoop開啟一個循環(huán)事件绎狭,并接受輸入事件,接受的事件來自兩種不同的來源:
    1.輸入源(input source)(傳遞異步事件)
    2.定時源(timer source)(傳遞同步事件)
    RunLoop接收到消息后采用handlePort褥傍、customSrc儡嘶、mySelector和timerFired等四個方法處理對應(yīng)的事件
    當(dāng)RunLoop沒有接收到消息時,則進(jìn)入休眠狀態(tài)恍风,以保持程序持續(xù)運(yùn)行蹦狂。

  • 應(yīng)用范疇:
    1.定時器(Timer)
    2.PerformSelector
    3.GCD Async Main Queue
    4.事件響應(yīng)誓篱、手勢識別、界面刷新
    5.網(wǎng)絡(luò)請求 √ AutoreleasePool

  • RunLoop在實(shí)際開中的應(yīng)用
    1.控制線程生命周期(線程笨ǎ活)
    2.解決NSTimer在滑動時停止工作的問題
    3.監(jiān)控應(yīng)用卡頓
    4.性能優(yōu)化

  • 運(yùn)行邏輯
    01窜骄、通知Observers:進(jìn)入Loop 02、通知Observers:即將處理Timers 03摆屯、通知Observers:即將處理Sources 04邻遏、處理Blocks 05、處理Source0(可能會再次處理Blocks) 06虐骑、如果存在Source1准验,就跳轉(zhuǎn)到第8步 07、通知Observers:開始休眠(等待消息喚醒) 08廷没、通知Observers:結(jié)束休眠(被某個消息喚醒) 01> 處理Timer 02> 處理GCD Async To Main Queue 03> 處理Source1 09糊饱、處理Blocks 10、根據(jù)前面的執(zhí)行結(jié)果颠黎,決定如何操作 01> 回到第02步 02> 退出Loop 11另锋、通知Observers:退出Loop

  • RunLoop的結(jié)構(gòu)組成

RunLoop位于蘋果的Core Foundation庫中,而Core Foundation庫則位于iOS架構(gòu)分層的Core Service層中(值得注意的是狭归,Core Foundation是一個跨平臺的通用庫砰蠢,不僅支持Mac,iOS唉铜,同時也支持Windows):

  • 六個被調(diào)起方法
    主線程 (有 RunLoop 的線程) 幾乎所有函數(shù)都從以下六個之一的函數(shù)調(diào)起:

CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function

用于向外部報(bào)告 RunLoop 當(dāng)前狀態(tài)的更改,框架中很多機(jī)制都由 RunLoopObserver 觸發(fā)律杠,如 CAAnimation

CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block

消息通知潭流、非延遲的perform、dispatch調(diào)用柜去、block回調(diào)灰嫉、KVO

CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queue
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function

延遲的perform, 延遲dispatch調(diào)用

CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function

處理App內(nèi)部事件、App自己負(fù)責(zé)管理(觸發(fā))嗓奢,如UIEvent讼撒、CFSocket。普通函數(shù)調(diào)用股耽,系統(tǒng)調(diào)用

CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function

由RunLoop和內(nèi)核管理根盒,Mach port驅(qū)動,如CFMachPort物蝙、CFMessagePort

二. RunLoop基本作用

保持程序持續(xù)運(yùn)行炎滞,程序一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應(yīng)的RunLoop,RunLoop保證主線程不會被銷毀诬乞,也就保證了程序的持續(xù)運(yùn)行

處理App中的各種事件(比如:觸摸事件册赛,定時器事件钠导,Selector事件等)

節(jié)省CPU資源,提高程序性能森瘪,程序運(yùn)行起來時牡属,當(dāng)什么操作都沒有做的時候,RunLoop就告訴CUP扼睬,現(xiàn)在沒有事情做逮栅,我要去休息,這時CUP就會將其資源釋放出來去做其他的事情痰驱,當(dāng)有事情做的時候RunLoop就會立馬起來去做事情

我們先通過API內(nèi)一張圖片來簡單看一下RunLoop內(nèi)部運(yùn)行原理


RunLoop內(nèi)部運(yùn)行原理

通過圖片可以看出证芭,RunLoop在跑圈過程中,當(dāng)接收到Input sources 或者 Timer sources時就會交給對應(yīng)的處理方去處理担映。當(dāng)沒有事件消息傳入的時候废士,RunLoop就休息了。這里只是簡單的理解一下這張圖蝇完,接下來我們來了解RunLoop對象和其一些相關(guān)類官硝,來更深入的理解RunLoop運(yùn)行流程。

三. RunLoop在哪里開啟

UIApplicationMain函數(shù)內(nèi)啟動了Runloop短蜕,程序不會馬上退出氢架,而是保持運(yùn)行狀態(tài)。因此每一個應(yīng)用必須要有一個runloop朋魔,

我們知道主線程一開起來岖研,就會跑一個和主線程對應(yīng)的RunLoop,那么RunLoop一定是在程序的入口main函數(shù)中開啟警检。

Runloop入口

進(jìn)入UIApplicationMain

我們發(fā)現(xiàn)它返回的是一個int數(shù)孙援,那么我們對main函數(shù)做一些修改

運(yùn)行程序,我們發(fā)現(xiàn)只會打印開始扇雕,并不會打印結(jié)束拓售,這說明在UIApplicationMain函數(shù)中,開啟了一個和主線程相關(guān)的RunLoop镶奉,導(dǎo)致UIApplicationMain不會返回础淤,一直在運(yùn)行中,也就保證了程序的持續(xù)運(yùn)行哨苛。

我們來看到RunLoop的源碼

// 用DefaultMode啟動

voidCFRunLoopRun(void) {/* DOES CALLOUT */

int32_t result;

do{

 result =CFRunLoopRunSpecific(CFRunLoopGetCurrent(),kCFRunLoopDefaultMode,1.0e10,false); CHECK_FOR_FORK(); 

 }while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);

}

我們發(fā)現(xiàn)RunLoop確實(shí)是do while通過判斷result的值實(shí)現(xiàn)的鸽凶。因此,我們可以把RunLoop看成一個死循環(huán)建峭。如果沒有RunLoop吱瘩,UIApplicationMain函數(shù)執(zhí)行完畢之后將直接返回,也就沒有程序持續(xù)運(yùn)行一說了迹缀。

四. RunLoop對象

Fundation框架  (基于CFRunLoopRef的封裝)

NSRunLoop對象

CoreFoundation

CFRunLoopRef對象

因?yàn)镕undation框架是基于CFRunLoopRef的一層OC封裝使碾,這里我們主要研究CFRunLoopRef源碼

如何獲得RunLoop對象


Foundation[NSRunLoopcurrentRunLoop];// 獲得當(dāng)前線程的RunLoop對象

[NSRunLoopmainRunLoop];// 獲得主線程的RunLoop對象Core 

FoundationCFRunLoopGetCurrent();// 獲得當(dāng)前線程的RunLoop對象

CFRunLoopGetMain();// 獲得主線程的RunLoop對象

RunLoop接收幾種輸入源蜜徽,系統(tǒng)默認(rèn)定義了幾種模式?

  • 輸入源有兩種
    基于端口的輸入源(port)
    自定義的輸入源(custom)
  • 系統(tǒng)定義的RunLoop模式有五種
    最常用的有三種票摇,如下所示:
    1.NSDefaultRunLoopMode
    默認(rèn)模式拘鞋,主線程中默認(rèn)是NSDefaultRunLoopMode
    2.UITrackingRunLoopMode
    視圖滾動模式,RunLoop會處于該模式下
    3.NSRunLoopCommonModes
    并不是真正意義上的Mode矢门,是一個占位用的“Mode”盆色,默認(rèn)包含了NSDefaultRunLoopMode和UITrackingRunLoopMode兩種模式

RunLoop模式的原理和使用注意點(diǎn)?
原理和注意點(diǎn)

  • 一個RunLoop包含若干個Mode祟剔,每個Mode又包含若干個Source隔躲、Observer、Timer(如下圖所示)
  • 每次RunLoop啟動物延,只能指定一個Mode宣旱,這個Mode被稱為CurrentMode
  • 如果需要切換Mode,只能退出Loop叛薯,再重新指定一個Mode進(jìn)入浑吟, 以使不同組之間的Source、Observer耗溜、Timer互不受影響

在 CoreFoundation 里面關(guān)于 RunLoop 有5個類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

其中 CFRunLoopModeRef 類并沒有對外暴露组力,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝

RunLoopMode

一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer抖拴。每次調(diào)用 RunLoop 的主函數(shù)時燎字,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode阿宅。如果需要切換 Mode候衍,只能退出 Loop,再重新指定一個 Mode 進(jìn)入家夺。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響伐弹。

CFRunLoopSourceRef 是事件產(chǎn)生的地方拉馋。Source有兩個版本:Source0 和 Source1。
? Source0 只包含了一個回調(diào)(函數(shù)指針)惨好,它并不能主動觸發(fā)事件煌茴。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source)日川,將這個 Source 標(biāo)記為待處理蔓腐,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件龄句。
? Source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針)回论,被用于通過內(nèi)核和其他線程相互發(fā)送消息散罕。這種 Source 能主動喚醒 RunLoop 的線程,其原理在下面會講到傀蓉。

CFRunLoopTimerRef 是基于時間的觸發(fā)器欧漱,它和 NSTimer 是toll-free bridged 的,可以混用葬燎。其包含一個時間長度和一個回調(diào)(函數(shù)指針)误甚。當(dāng)其加入到 RunLoop 時,RunLoop會注冊對應(yīng)的時間點(diǎn)谱净,當(dāng)時間點(diǎn)到時窑邦,RunLoop會被喚醒以執(zhí)行那個回調(diào)。

CFRunLoopObserverRef 是觀察者壕探,每個 Observer 都包含了一個回調(diào)(函數(shù)指針)冈钦,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化浩蓉∨杉蹋可以觀測的時間點(diǎn)有以下幾個:


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即將進(jìn)入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
};

上面的 Source/Timer/Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 mode捻艳。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的驾窟。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出认轨,不進(jìn)入循環(huán)绅络。

通過上面分析我們知道,CFRunLoopModeRef代表RunLoop的運(yùn)行模式嘁字,一個RunLoop包含若干個Mode恩急,每個Mode又包含若干個Source0/Source1/Timer/Observer,而RunLoop啟動時只能選擇其中一個Mode作為currentMode纪蜒。

Source1/Source0/Timers/Observer分別代表什么

  1. Source1 : 基于Port的線程間通信

  2. Source0 : 觸摸事件衷恭,PerformSelectors

我們通過代碼驗(yàn)證一下


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"點(diǎn)擊了屏幕");
}

打斷點(diǎn)之后打印堆棧信息,當(dāng)xcode工具區(qū)打印的堆棧信息不全時纯续,可以在控制臺通過“bt”指令打印完整的堆棧信息随珠,由堆棧信息中可以發(fā)現(xiàn),觸摸事件確實(shí)是會觸發(fā)Source0事件猬错。

touchesBegan堆棧信息

同樣的方式驗(yàn)證performSelector堆棧信息

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
});

可以發(fā)現(xiàn)PerformSelectors同樣是觸發(fā)Source0事件

performSelector堆棧信息
  1. Timers : 定時器窗看,NSTimer

通過代碼驗(yàn)證


[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
    NSLog(@"NSTimer ---- timer調(diào)用了");
}];

打印完整堆棧信息

Timer 堆棧信息
  1. Observer : 監(jiān)聽器,用于監(jiān)聽RunLoop的狀態(tài)

Source

即可以喚醒Runloop的一些事件倦炒。比如用戶點(diǎn)擊了屏幕显沈,就會創(chuàng)建一個input source。

  • source0 : 非系統(tǒng)事件

只包含了一個回調(diào)(函數(shù)指針)逢唤,它并不能主動觸發(fā)事件拉讯。使用時涤浇,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理遂唧,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop芙代,讓其處理這個事件。

  • source1 : 系統(tǒng)事件

包含了一個 mach_port和一個回調(diào)(函數(shù)指針)盖彭,被用于通過內(nèi)核和其他線程相互發(fā)送消息纹烹。這種 Source 能主動喚醒 RunLoop 的線程

Timer

我們經(jīng)常用的NSTimer就屬于這一類。

Observer

某個observer可以監(jiān)聽runloop的狀態(tài)變化召边,并作出一定反應(yīng)铺呵。

RunLoop運(yùn)行流程


經(jīng)典大圖

RunLoop 結(jié)構(gòu)組成

RunLoop位于蘋果的Core Foundation庫中,而Core Foundation庫則位于iOS架構(gòu)分層的Core Service層中(值得注意的是隧熙,Core Foundation是一個跨平臺的通用庫片挂,不僅支持Mac,iOS贞盯,同時也支持Windows):

五. RunLoop和線程間的關(guān)系

每條線程都有唯一的一個與之對應(yīng)的RunLoop對象

RunLoop保存在一個全局的Dictionary里音念,線程作為key,RunLoop作為value

主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建

RunLoop在第一次獲取時創(chuàng)建躏敢,在線程結(jié)束時銷毀

通過源碼查看上述對應(yīng)

// 拿到當(dāng)前Runloop 調(diào)用_CFRunLoopGet0CFRunLoopRefCFRunLoopGetCurrent(void) { CHECK_FOR_FORK();

CFRunLoopRefrl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if(rl)returnrl;return_CFRunLoopGet0(pthread_self());

}

// 查看_CFRunLoopGet0方法內(nèi)部CF_EXPORTCFRunLoopRef_CFRunLoopGet0(pthread_t t) {if(pthread_equal(t, kNilPthreadT)) { 

 t = pthread_main_thread_np(); 

 }

 __CFLock(&loopsLock);if(!__CFRunLoops) { __CFUnlock(&loopsLock);CFMutableDictionaryRefdict =CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0,NULL, &kCFTypeDictionaryValueCallBacks);

// 根據(jù)傳入的主線程獲取主線程對應(yīng)的RunLoop

CFRunLoopRefmainLoop = __CFRunLoopCreate(pthread_main_thread_np());

// 保存主線程 將主線程-key和RunLoop-Value保存到字典中

CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

if(!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void*volatile*)&__CFRunLoops)) {CFRelease(dict); 

 }CFRelease(mainLoop); __CFLock(&loopsLock);

 }

// 從字典里面拿闷愤,將線程作為key從字典里獲取一個loop

CFRunLoopRefloop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

 __CFUnlock(&loopsLock);

// 如果loop為空,則創(chuàng)建一個新的loop件余,所以runloop會在第一次獲取的時候創(chuàng)建

if(!loop) {

CFRunLoopRefnewLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); 

 loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

// 創(chuàng)建好之后讥脐,以線程為key runloop為value,一對一存儲在字典中啼器,下次獲取的時候旬渠,則直接返回字典內(nèi)的runloop

if(!loop) {

CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; 

 }

// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it

__CFUnlock(&loopsLock);

CFRelease(newLoop); 

 }

if(pthread_equal(t, pthread_self())) {

 _CFSetTSD(__CFTSDKeyRunLoop, (void*)loop,NULL);

if(0== _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void*)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void(*)(void*))__CFFinalizeRunLoop); 

 }

 }

returnloop;

}

從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的端壳,其關(guān)系是保存在一個 Dictionary 里告丢。所以我們創(chuàng)建子線程RunLoop時,只需在子線程中獲取當(dāng)前線程的RunLoop對象即可[NSRunLoop currentRunLoop];如果不獲取损谦,那子線程就不會創(chuàng)建與之相關(guān)聯(lián)的RunLoop岖免,并且只能在一個線程的內(nèi)部獲取其 RunLoop

[NSRunLoop currentRunLoop];方法調(diào)用時,會先看一下字典里有沒有存子線程相對用的RunLoop成翩,如果有則直接返回RunLoop觅捆,如果沒有則會創(chuàng)建一個赦役,并將與之對應(yīng)的子線程存入字典中麻敌。當(dāng)線程結(jié)束時,RunLoop會被銷毀掂摔。

NSTimer和RunLoop的關(guān)系术羔?

  • NSTimer需要添加到Runloop中赢赊, 才能執(zhí)行的情況

NSTimer *timer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(update) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

  • NSTimer默認(rèn)被添加到Runloop中, 直接執(zhí)行的情況

[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(update) userInfo:nil repeats:YES];

NSTimer準(zhǔn)確嗎级历,如果不準(zhǔn)確释移,如何設(shè)計(jì)一個準(zhǔn)確的timer?
不準(zhǔn)確
準(zhǔn)確的Timer應(yīng)該和當(dāng)前線程的RunLoopMode保持一致

TableView/ScrollView/CollectionView滾動時為什么NSTimer會停止寥殖?

一個RunLoop不能同時共存兩個mode
當(dāng)滾動視圖滾動時玩讳,當(dāng)前RunLoop處于UITrackingRunLoopMode,
NSTimer的RunLoopMode和當(dāng)前線程的RunLoopMode不一致嚼贡,所以會停止
解決方法:將timer的runloopMode改為UITrackingRunLoopMode或者NSRunLoopCommonModes

如果NSTimer在分線程中創(chuàng)建熏纯,會發(fā)生什么,應(yīng)該注意什么粤策?

  • NSTimer沒有啟動
    -- 在主線程中樟澜,系統(tǒng)默認(rèn)創(chuàng)建并啟動主線程的runloop
    -- 在分線程中,系統(tǒng)不會自動啟動runloop叮盘,需要手動啟動
  • 解決方法:
    啟動分線程的runLoop

在異步線程中下載很多圖片,如果失敗了,該如何處理?請結(jié)合RunLoop來談?wù)劷鉀Q方案

在異步線程中啟動一個RunLoop重新發(fā)送網(wǎng)絡(luò)請求,下載圖片

如果程序啟動就需要執(zhí)行一個耗時操作秩贰,你會怎么做?

開啟一個異步的子線程柔吼,并啟動它的RunLoop來執(zhí)行該耗時操作

runloop與autoreleasepool的關(guān)系,如果在分線程中啟動一個異步請求毒费,會有什么問題?

判斷其是否請求結(jié)束嚷堡,如果未結(jié)束蝗罗,要保持當(dāng)前線程一直啟動,直到結(jié)束


while(!isFinish)
     {
       [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
     }

可以看到蝌戒,實(shí)際上 RunLoop 就是這樣一個函數(shù)串塑,其內(nèi)部是一個 do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時北苟,線程就會一直停留在這個循環(huán)里桩匪;直到超時或被手動停止,該函數(shù)才會返回友鼻。

程序啟動時傻昙,runloop是如何工作的?如果程序啟動就需要執(zhí)行一個耗時操作彩扔,你會怎么做妆档?

程序啟動時,系統(tǒng)默認(rèn)創(chuàng)建并啟動主線程的runloop虫碉,runloop會默認(rèn)創(chuàng)建兩個Observe來進(jìn)行監(jiān)聽runloop的進(jìn)出和睡眠贾惦,有事情的時候就去做,沒事的休眠。

(線程(創(chuàng)建)-->runloop將進(jìn)入-->最高優(yōu)先級OB創(chuàng)建釋放池-->runloop將睡-->最低優(yōu)先級OB銷毀舊池創(chuàng)建新池-->runloop將退出-->最低優(yōu)先級OB銷毀新池-->線程(銷毀))

線程剛創(chuàng)建時并沒有runloop须板,如果你不主動去獲取碰镜,那么一直都不會有。

耗時操作可以放在分線程中進(jìn)行习瑰,結(jié)束后回到主線程绪颖。

經(jīng)典面試題

Runloop和線程是什么關(guān)系?
每條線程都有唯一的一個與之對應(yīng)的RunLoop對象甜奄,其關(guān)系是保存在一個全局的 Dictionary 里柠横;主線程的RunLoop已經(jīng)自動創(chuàng)建,子線程的RunLoop需要主動創(chuàng)建课兄;RunLoop在第一次獲取時創(chuàng)建滓鸠,在線程結(jié)束時銷毀

Runloop的mode作用是什么?
指定事件在運(yùn)行循環(huán)中的優(yōu)先級的第喳,

線程的運(yùn)行需要不同的模式糜俗,去響應(yīng)各種不同的事件,去處理不同情境模式曲饱。(比如可以優(yōu)化tableview的時候可以設(shè)置UITrackingRunLoopMode下不進(jìn)行一些操作悠抹,比如設(shè)置圖片等。)

以+scheduledTimerWithTimeInterval:的方式觸發(fā)的timer扩淀,在滑動頁面上的列表時楔敌,timer會暫停回調(diào)驻谆, 為什么卵凑?
滑動scrollView時,主線程的RunLoop會切換到UITrackingRunLoopMode這個Mode胜臊,執(zhí)行的也是UITrackingRunLoopMode下的任務(wù)(Mode中的item)勺卢,而timer是添加在NSDefaultRunLoopMode下的,所以timer任務(wù)并不會執(zhí)行象对,只有當(dāng)UITrackingRunLoopMode的任務(wù)執(zhí)行完畢黑忱,runloop切換到NSDefaultRunLoopMode后,才會繼續(xù)執(zhí)行timer勒魔。

如何解決在滑動頁面上的列表時甫煞,timer會暫停回調(diào)冠绢?
將Timer放到NSRunLoopCommonModes中執(zhí)行即可

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run];復(fù)制代碼
NSTImer使用時需要注意什么抚吠?
注意timer添加到runloop時應(yīng)該設(shè)置為什么mode

注意timer在不需要時,一定要調(diào)用invalidate方法使定時器失效弟胀,否則得不到釋放

RunLoop 有哪些應(yīng)用楷力?
常駐內(nèi)存蕊玷、AutoreleasePool 自動釋放池

AutoreleasePool 和 RunLoop 有什么聯(lián)系?
iOS應(yīng)用啟動后會注冊兩個 Observer 管理和維護(hù) AutoreleasePool弥雹。應(yīng)用程序剛剛啟動時默認(rèn)注冊了很多個Observer,其中有兩個Observer的 callout 都是 _ wrapRunLoopWithAutoreleasePoolHandler延届,這兩個是和自動釋放池相關(guān)的兩個監(jiān)聽剪勿。

第一個 Observer 會監(jiān)聽 RunLoop 的進(jìn)入,它會回調(diào)objc_autoreleasePoolPush() 向當(dāng)前的 AutoreleasePoolPage 增加一個哨兵對象標(biāo)志創(chuàng)建自動釋放池方庭。這個 Observer 的 order 是 -2147483647 優(yōu)先級最高厕吉,確保發(fā)生在所有回調(diào)操作之前。

第二個 Observer 會監(jiān)聽 RunLoop 的進(jìn)入休眠和即將退出 RunLoop 兩種狀態(tài)械念,在即將進(jìn)入休眠時會調(diào)用 objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根據(jù)情況從最新加入的對象一直往前清理直到遇到哨兵對象头朱。而在即將退出 RunLoop 時會調(diào)用objc_autoreleasePoolPop() 釋放自動自動釋放池內(nèi)對象。這個Observer 的 order 是 2147483647 龄减,優(yōu)先級最低项钮,確保發(fā)生在所有回調(diào)操作之后。

NSRunLoop 和 CFRunLoopRef 區(qū)別
CFRunLoopRef 基于C 線程安全希停,NSRunLoop 基于 CFRunLoopRef 面向?qū)ο蟮腁PI 是不安全的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烁巫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宠能,更是在濱河造成了極大的恐慌亚隙,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,002評論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件违崇,死亡現(xiàn)場離奇詭異阿弃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)羞延,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評論 3 400
  • 文/潘曉璐 我一進(jìn)店門渣淳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伴箩,你說我怎么就攤上這事水由。” “怎么了赛蔫?”我有些...
    開封第一講書人閱讀 169,787評論 0 365
  • 文/不壞的土叔 我叫張陵砂客,是天一觀的道長。 經(jīng)常有香客問我呵恢,道長鞠值,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,237評論 1 300
  • 正文 為了忘掉前任渗钉,我火速辦了婚禮彤恶,結(jié)果婚禮上钞钙,老公的妹妹穿的比我還像新娘。我一直安慰自己声离,他們只是感情好芒炼,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著术徊,像睡著了一般本刽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赠涮,一...
    開封第一講書人閱讀 52,821評論 1 314
  • 那天子寓,我揣著相機(jī)與錄音,去河邊找鬼笋除。 笑死斜友,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垃它。 我是一名探鬼主播鲜屏,決...
    沈念sama閱讀 41,236評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼国拇!你這毒婦竟也來了墙歪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,196評論 0 277
  • 序言:老撾萬榮一對情侶失蹤贝奇,失蹤者是張志新(化名)和其女友劉穎虹菲,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評論 3 343
  • 正文 我和宋清朗相戀三年醇疼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霎褐。...
    茶點(diǎn)故事閱讀 40,928評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖该镣,靈堂內(nèi)的尸體忽然破棺而出冻璃,到底是詐尸還是另有隱情,我是刑警寧澤损合,帶...
    沈念sama閱讀 36,583評論 5 351
  • 正文 年R本政府宣布省艳,位于F島的核電站,受9級特大地震影響嫁审,放射性物質(zhì)發(fā)生泄漏跋炕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評論 3 336
  • 文/蒙蒙 一律适、第九天 我趴在偏房一處隱蔽的房頂上張望辐烂。 院中可真熱鬧遏插,春花似錦、人聲如沸纠修。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扣草。三九已至了牛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間德召,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評論 1 274
  • 我被黑心中介騙來泰國打工汽纤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留上岗,地道東北人。 一個月前我還...
    沈念sama閱讀 49,378評論 3 379
  • 正文 我出身青樓蕴坪,卻偏偏與公主長得像肴掷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子背传,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 概述 RunLoop作為iOS中一個基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系呆瞻,同時也是很多常見技術(shù)的幕后功臣。盡管在平時多...
    sumrain_cloud閱讀 952評論 0 5
  • Runloop是iOS和OSX開發(fā)中非尘毒粒基礎(chǔ)的一個概念痴脾,從概念開始學(xué)習(xí)。 RunLoop的概念 -般說梳星,一個線程一...
    小貓仔閱讀 999評論 0 1
  • RunLoop簡介 從字面意思來看是運(yùn)行循環(huán)赞赖,在程序運(yùn)行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就...
    一直很安靜_25ae閱讀 407評論 0 0
  • RunLoop基本作用: 1.保持程序持續(xù)運(yùn)行 2.處理App中的各種事件 3.節(jié)省CPU資源冤灾,提高程序性能 Ru...
    BabyNeedCare閱讀 297評論 0 0
  • 一.RunLoop簡介 運(yùn)行循環(huán)前域,在程序運(yùn)行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就會立即退出韵吨,...
    Jt_Self閱讀 326評論 0 0