RunLoop是iOS事件響應(yīng)與任務(wù)處理最核心的機(jī)制,它貫穿iOS整個(gè)系統(tǒng),自動(dòng)釋放池,延遲處理,觸摸事件,屏幕刷新都是通過RunLoop實(shí)現(xiàn)的.Foundation中的NSRunLoop和Core Foundation中CFRunLoop 是RunLoop的主要實(shí)現(xiàn).
基礎(chǔ)實(shí)現(xiàn)
RunLoop通過do-while循環(huán)保持整個(gè)App的持續(xù)運(yùn)行,同時(shí)能在運(yùn)行和睡眠狀態(tài)之間切換,節(jié)省CPU資源.Android中的Looper跟iOS中的RunLoop類似,接收異步消息,控制應(yīng)用程序的生命周期.一般情況一個(gè)線程只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后就會退出.我們可以通過Runloop保證線程能隨時(shí)處理事件,并不退出.
Apple不允許直接創(chuàng)建 RunLoop,提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent().
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
pthread_self獲取當(dāng)前線程,pthread_main_thread_np獲取主線程,通過線程獲取當(dāng)前的Runloop.線程和Runloop被保存在全局字典__CFRunLoops中,如果字典中存在則會取出,如果不存在則會創(chuàng)建.
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
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);
}
}
return loop;
}
Runloop運(yùn)行:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
運(yùn)行機(jī)制
RunLoop是線程中的一個(gè)循環(huán),在循環(huán)中不斷檢測通過Input sources(輸入源)和Timer sources(定時(shí)源)兩種來源等待接受事件,然后對接受到的事件通知線程進(jìn)行處理,并在沒有事件的時(shí)候進(jìn)行睡眠.
RunLoop與線程之間的關(guān)系密不可分:
1.線程與RunLoop是一一對應(yīng)的,一個(gè)線程對應(yīng)一個(gè)RunLoop對象,根RunLoop可以嵌套子RunLoop.
2.主線程的RunLoop在應(yīng)用啟動(dòng)的時(shí)候會自動(dòng)創(chuàng)建,非主線程的RunLoop需要在該線程自己啟動(dòng).
3.RunLoop對象在第一次獲取RunLoop時(shí)創(chuàng)建,銷毀則是在線程結(jié)束的時(shí)候.
4.不能自己創(chuàng)建RunLoop.
5.RunLoop并不是線程安全的,只能在當(dāng)前線程中操作當(dāng)前線程的RunLoop,而不能去操作其他線程的RunLoop,同時(shí)也需要避免在其他線程上調(diào)用當(dāng)前線程的RunLoop.
一個(gè) RunLoop 對象包含若干個(gè) Mode 對象,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer,RunLoop 一次運(yùn)行只能在一個(gè) Mode 之下,如果需要切換 Mode,需要退出 Loop 才能重新指定一個(gè) Mode.這樣做主要是為了分隔開不同組Source/Timer/Observer,讓其互不影響.
一個(gè) Source 對象是一個(gè)事件,Source 有兩個(gè)版本:Source0 和 Source1,Source0 只包含一個(gè)函數(shù)指針,并不能主動(dòng)觸發(fā),需要將 Source0 標(biāo)記為待處理,在 RunLoop 運(yùn)轉(zhuǎn)的時(shí)候,才會處理這個(gè)事件(如果 RunLoop 處于休眠狀態(tài),則不會被喚醒去處理),而 Source1 包含了一個(gè) mach_port 和一個(gè)函數(shù)指針,mach_port 是 iOS 系統(tǒng)提供的基于端口的輸入源,可用于線程或進(jìn)程間通訊。而 RunLoop 支持的輸入源類型中就包括基于端口的輸入源,可以做到對 mach_port 端口源事件的監(jiān)聽。所以監(jiān)聽到 source1 端口的消息時(shí),RunLoop 就會自己醒來去執(zhí)行 Source1 事件(也能稱為被消息喚醒)。也就是 Source0 是直接添加給 RunLoop 處理的事件,而 Source1 是基于端口的,進(jìn)程或線程之間傳遞消息觸發(fā)的事件.
Timer 是基于時(shí)間的觸發(fā)器,CFRunLoopTimerRef 和 NSTimer 可以通過 Toll-free bridging 技術(shù)混用,Toll-free bridging 是一種允許某些 ObjC 類與其對應(yīng)的 CoreFoundation 類之間可以互換使用的機(jī)制,當(dāng)將 Timer 加入到 RunLoop 時(shí),RunLoop 會注冊對應(yīng)的時(shí)間點(diǎn),當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop 會被喚醒以執(zhí)行 Timer 回調(diào).
__CFRunLoopMode定義如下:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

基礎(chǔ)知識
RunLoop運(yùn)行狀態(tài)通過CFRunLoopActivity可以查看:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

- kCFRunLoopEntry -- 進(jìn)入runloop循環(huán)
- kCFRunLoopBeforeTimers -- 處理定時(shí)調(diào)用前回調(diào)
- kCFRunLoopBeforeSources -- 處理input sources的事件
- kCFRunLoopBeforeWaiting -- runloop睡眠前調(diào)用
- kCFRunLoopAfterWaiting -- runloop喚醒后調(diào)用
- kCFRunLoopExit -- 退出runloop
CoreFoundation中關(guān)于RunLoop的5個(gè)重要的類:
- CFRunLoopRef:運(yùn)行循環(huán)對象,也就是它自身.
- CFRunLoopModeRef:指定runloop的運(yùn)行模式.給事件源分組,避免互相影響.一個(gè)runLoop可以有很多個(gè)Mode,1個(gè)Mode可以有很多個(gè)Source,Observer,Timer,但是在同一時(shí)刻只能同時(shí)執(zhí)行一種Mode.
- CFRunLoopSourceRef:輸入源.
- CFRunLoopTimerRef:定時(shí)源,定時(shí)器,必須加入到runloop.
- CFRunLoopObserverRef(觀察者,觀察是否有事件).
關(guān)于五個(gè)主要的類可以描述為一個(gè)RunLoop對象中包含若干個(gè)運(yùn)行模式(CFRunLoopModeRef),而每一個(gè)運(yùn)行模式下又包含若干個(gè)輸入源(CFRunLoopSourceRef)、定時(shí)源(CFRunLoopTimerRef)、觀察者(CFRunLoopObserverRef).
單次Runloop可以處理Source1(觸摸/鎖屏/搖晃),Source0事件(需要手動(dòng)觸發(fā)),Timer事件和觀察者事件.

Runloop 模式
系統(tǒng)默認(rèn)定義了多種運(yùn)行模式(CFRunLoopModeRef),常見的有五種:
kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的.
UITrackingRunLoopMode:界面跟蹤 Mode,用于ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode 影響.
UIInitializationRunLoopMode:在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè)Mode,啟動(dòng)完成之后就不再使用.
GSEventReceiveRunLoopMode:接收系統(tǒng)事件的內(nèi)部 Mode,通常用不到.
kCFRunLoopCommonModes:占位用的 Mode,不是一種真正的 Mode.
RunLoop與自動(dòng)釋放池:
自動(dòng)釋放池寄生于Runloop,程序啟動(dòng)后,主線程注冊了兩個(gè)Observer監(jiān)聽runloop的進(jìn)出和睡眠.一個(gè)最高優(yōu)先級OB監(jiān)測Entry狀態(tài),一個(gè)最低優(yōu)先級OB監(jiān)聽BeforeWaiting狀態(tài)和Exit狀態(tài).
RunLoop 實(shí)戰(zhàn)
1.維護(hù)線程的生命周期,讓線程不自動(dòng)退出,isFinished為Yes時(shí)退出.
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!self.isCancelled && !self.isFinished) {
@autoreleasepool {
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
}
2.創(chuàng)建常駐線程,執(zhí)行一些會一直存在的任務(wù),該線程的生命周期跟App相同:
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
創(chuàng)建常駐線程最經(jīng)典的例子是AFNetWorking 2.x版本中代碼:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
3.在一定時(shí)間內(nèi)監(jiān)聽某種事件,或執(zhí)行某種任務(wù)的線程
如下代碼,在30分鐘內(nèi),每隔30s執(zhí)行onTimerFired.這種場景一般會出現(xiàn)在,如我需要在應(yīng)用啟動(dòng)之后,在一定時(shí)間內(nèi)持續(xù)更新某項(xiàng)數(shù)據(jù),如果用來監(jiān)控屏幕的卡頓也可以.
@autoreleasepool {
NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
target:self
selector:@selector(onTimerFired:)
userInfo:nil
repeats:YES];
[runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
4.UITableView滾動(dòng)加載圖片
當(dāng)tableView的cell上有需要從網(wǎng)絡(luò)獲取的圖片的時(shí)候,滾動(dòng)tableView,異步線程回去加載圖片,加載完成后主線程會設(shè)置cell的圖片,但是會造成卡頓??梢栽O(shè)置圖片的任務(wù)在CFRunloopDefaultMode下進(jìn)行,當(dāng)滾動(dòng)tableView的時(shí)候,Runloop切換到UITrackingRunLoopMode,不去設(shè)置圖片,而是而是當(dāng)停止的時(shí)候,再去設(shè)置圖片.
[self performSelector:@selector(download:) withObject:url afterDelay:0 inModes:NSDefaultRunLoopMode];
5.NSTimer失效
如果頁面有計(jì)時(shí)器同時(shí)有滑動(dòng)視圖的時(shí)候,需要注意NSTimer的模式,視圖滑動(dòng)的過程會切換至UITrackingMode模式下,造成Timer短暫失效,將Timer的模式設(shè)置為CommonMode即可.
self.upTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(upTimeUpdate) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.upTimer forMode:NSRunLoopCommonModes];
[self.upTimer fire];
self.bottomTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(bottomTimeUpdate) userInfo:nil repeats:YES];
參考資料:
CF框架源碼
RunLoop 原理和核心機(jī)制
CoreFoundation
深入理解RunLoop
滑動(dòng)卡頓優(yōu)化
官方文檔
CF源碼
http://blog.raozhizhen.com/post/2016-08-18