最近研究其他知識的時候,發(fā)現(xiàn)有用到Runloop,所以特地研究下。
什么是RunLoop
RunLoop用咱們的大白話就是跑圈,官方一點叫運行循環(huán)。
作用
- 保持程序一直在運行著。
- 處理咱們App的各種事件(如觸摸事件,定時器,Selector事件)
- 節(jié)省CPU的資源,提高程序的性能,該干活干活,該休息休息。
講個容易理解的??。
假如我開著個寵物店,好多人都把寵物寄養(yǎng)到我的店里,所以我的店里好多的寵物,我得照顧啊,一會這個餓了,一會這個渴了。一會這個要拉,一會這個要撒。假如我是個鐵人啊,我不吃不喝就照顧他們,如果沒事了,我就休息會,如果哪個狗狗要吃飯,我就去喂它。RunLoop就像我一樣,一直的看著是不是有事件過來,過來了處理了下。沒事了休息。
如果沒有RunLoop
如果沒有RunLoop,return 0 之后程序就結(jié)束了。iOS其實在UIApplicationMain里開啟了一個RunLoop保證程序不死。
int main(int argc, char * argv[]) {
// @autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// }
NSLog(@"我在執(zhí)行");
return 0;
}
如果有了RunLoop
do-while循環(huán)一直的運行,所以不會走到return。runLoop就是這樣做的。
int main(int argc, char * argv[]) {
// @autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// }
BOOL run = YES;
do {
//執(zhí)行各種任務,處理各種事件
} while (run);
return 0;
}
main函數(shù)中的RunLoop
UIApplicationMain函數(shù)內(nèi)部會啟動一個RunLoop,所以一直沒有return。保持了程序持續(xù)的運行。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop對象
iOS中有2套API來訪問和使用RunLoop
一套是Fundation(純OC的)框架中的NSRunLoop
一套是Core Fundation(純C語言的)框架中的CFRunLoopRef, http://opensource.apple.com/tarballs/CF/
NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu),需要多研究CFRunLoopRef層面的API(Core Foundation層面)
RunLoop與線程
每條線程都有唯一的一個與之對應的RunLoop對象
主線程的Runloop系統(tǒng)已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要手動創(chuàng)建
RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀
蘋果不允許直接創(chuàng)建 RunLoop,它只提供了兩個自動獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 這兩個函數(shù)內(nèi)部的邏輯大概是下面這樣:
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問 loopsDic 時的鎖
static CFSpinLock_t loopsLock;
/// 獲取一個 pthread 對應的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次進入時,初始化全局Dic,并先為主線程創(chuàng)建一個 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
/// 直接從 Dictionary 里獲取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
/// 取不到時,創(chuàng)建一個
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
/// 注冊一個回調(diào),當線程銷毀時,順便也銷毀其對應的 RunLoop。
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}
從上面的代碼可以看出,線程和 RunLoop 之間是一一對應的,其關系是保存在一個全局的 Dictionary 里。線程剛創(chuàng)建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時,RunLoop 的銷毀是發(fā)生在線程結(jié)束時。你只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外)
獲得RunLoop對象
RunLoop對象是不讓創(chuàng)建的,如 alloc,只能通過類方法獲取,內(nèi)部實現(xiàn)是一個懶加載,如果對象為nil,就創(chuàng)建,如果不是nil就返回當前對象。
//Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
//Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
RunLoop相關類
Core Fundation中關于RunLoop的5個類
CFRunLoopRef: RunLoop對象
CFRunLoopModeRef: RunLoop運行模式.
CFRunLoopSoruceRef: 事件源(輸入源)
CFRunLoopTimerRef:基于時間的觸發(fā)器.
CFRunLoopObserverRef: 觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
CFRunLoopModeRef

CFRunLoopModeRef代表RunLoop的運行模式
一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入,這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
CFRunLoopModeRef的類型
系統(tǒng)默認注冊了5個Mode:我們一般就使用kCFRunLoopDefaultMode,UITrackingRunLoopMode,kCFRunLoopCommonModes其他的基本用不到。
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef
Source0:非基于Prot(端口)的,是用戶主動觸發(fā)的事件,Source0 只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
Source1:基于Prot(端口)的,通過內(nèi)核和其他線程相互發(fā)送消息
CFRunLoopTimerRef
是基于時間的觸發(fā)器,基本上說的就是NSTimer 。其包含一個時間長度和一個回調(diào)(函數(shù)指針)。當其加入到 RunLoop 時,RunLoop會注冊對應的時間點,當時間點到時,RunLoop會被喚醒以執(zhí)行那個回調(diào)
CFRunLoopObserverRef
CFRunLoopObserverRef 是觀察者,每個 Observer 都包含了一個回調(diào)(函數(shù)指針),當 RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化。可以觀測的時間點有以下幾個:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
上面的 Source/Timer/Observer 被統(tǒng)稱為 mode item,一個 item 可以被同時加入多個 mode。但一個 item 被重復加入同一個 mode 時是不會有效果的。如果一個 mode 中一個 item 都沒有,則 RunLoop 會直接退出,不進入循環(huán)。
RunLoop處理邏輯


/// 用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動,允許設置RunLoop超時時間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對應mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù),進入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
/// ? 一個基于 port 的Source 的事件。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,處理消息。
handle_msg:
/// 9.1 如果一個 Timer 到時間了,觸發(fā)這個Timer的回調(diào)。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進入loop時參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時,mode里沒空,loop也沒被停止,那繼續(xù)loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
可以看到,實際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)。當你調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止,該函數(shù)才會返回。證明了咱們一開始的結(jié)果。
RunLoop應用
NSTimer
看我寫的這篇文章 http://www.itdecent.cn/p/19aab8570ce3
ImageView顯示
為了解決由于滑動tableview的時候給imageView賦值造成的卡頓問題,可以這樣
// 只在NSDefaultRunLoopMode模式下顯示圖片
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
PerformSelector
如果要用PerformSelector做延時調(diào)用,就會用到runloop,PerformSelector里面封裝的是NSTimer。
[self performSelector:@selector(run) withObject:nil afterDelay:3.0];
常駐線程
AFNetworking希望能在后臺線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨創(chuàng)建了一個線程,并在這個線程中啟動了一個 RunLoop:
+ (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;
}
RunLoop 啟動前內(nèi)部必須要有至少一個 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個新的 NSMachPort 添加進去了。通常情況下,調(diào)用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個 port 發(fā)送消息到 loop 內(nèi);但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒有用于實際的發(fā)送消息。
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
當需要這個后臺線程執(zhí)行任務時,AFNetworking 通過調(diào)用 [NSObject performSelector:onThread:..] 將這個任務扔到了后臺線程的 RunLoop 中。
自動釋放池

我寫的這篇文章的自動釋放池部分,自動釋放池跟Runloop 的關系很大,http://www.itdecent.cn/p/13cbddbefa20
參考文章
YY大神的博客 http://blog.ibireme.com/2015/05/18/runloop/
孫源視頻 https://pan.baidu.com/s/1dFIfLAD