一. Runloop基本作用
1)保持程序的持續(xù)運行
處理APP中各種事件(比如觸摸事件,定時器事件,selector事件)
節(jié)省cup資源,提高程序性能:該做事時做事,該休息時休息。
main函數(shù)中的runLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
代碼UIApplicationMain函數(shù)內部啟動了一個runLoop,所以UIApplicationMain函數(shù)一直沒有返回,保持程序的持續(xù)運行。這個默認啟動runloop是跟主線程關聯(lián)的。
點擊UIApplicationMain這個函數(shù)我們發(fā)現(xiàn),它是有返回值的, UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nonnull * _Null_unspecified argv, NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);,這里我們可以測試一下,UIApplicationMain函數(shù)內部是一直執(zhí)行的,是啟動了一個runloop的,測試代碼如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"-------star--------");
int number = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"number:%d",number);
return number;
}
}
控制臺輸出如下:
2018-09-22 18:08:16.417670+0800 123Demo[919:39945] -------star--------
這里我們發(fā)現(xiàn)我們的number并沒有被輸出,因為UIApplicationMain函數(shù)一直沒有返回,UIApplicationMain函數(shù)內部一直在執(zhí)行。
二. RunLoop對象
iOS中有兩套API來訪問和使用RunLoop
- Foundation NSRunLoop
- Core Foundation CFRunLoopRef
Runloop相關資料 https://opensource.apple.com/source/CF/CF-1151.16/
鏈接 https://pan.baidu.com/s/14xMqWH47X5tex0KPjxkMAw 密碼:lx8u
三. RunLoop與線程
- Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的NSRunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的NSRunLoop對象
- Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的NSRunLoop對象
CFRunLoopGetMain(); // 獲得主線程的NSRunLoop對象
這里,我們可以看下CFRunLoop.c里面的核心源碼
// Do any additional setup after loading the view, typically from a nib.
// 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);
// 創(chuàng)建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 創(chuàng)建主線程對應的runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 使用字典保存主線程-主線程對應的runloop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 從字典中獲取子線程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果子線程的runloop不存在,那么就為該線程創(chuàng)建一個對應的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把當前子線程和對應的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);
}
}
return loop;
}
-
Tips
每條線程都有唯一的一個與之對應的RunLoop對象.
主線程RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
RunLoop在第一次獲取是創(chuàng)建,在線程結束時銷毀
三. RunLoop相關類
- Core Foundation中關于RunLoop的5個類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

在RunLoop中有多個運行模式,但是RunLoop只能選擇一種運行模式(如:一臺空調有制冷和制熱兩種模式,但是啟動后只能選擇一種模式)。
CFRunLoopModeRef代表RunLoop的運行模式:
一個RunLoop包含若干個Mode,每個Mode又包含若干個source/timer/oberrver。model里面至少要有一個
timer或者是source
每次RunLoop啟動時,只能選擇其中一個mode,這個mode被稱作currentMode.
如果需要切換mode,只能退出Loop,再重新指定一個Mode進入
CFRunLoopModeRef系統(tǒng)默認注冊了5個Mode:
kCFRunLoopDefaultMode: App的默認 Mode,通常主線程是在這個 Mode 下運行的。
UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響。
UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內部 Mode,通常用不到。
5: kCFRunLoopCommonModes: 這是一個占位的 Mode,沒有實際作用。
kCFRunLoopCommonModes模式有一個比較經(jīng)典的例子,實現(xiàn)一個精準的timer。
例:一個tableview上有一個定時器,這時應該用kCFRunLoopCommonModes模式。
- (void)timer {
// 1.創(chuàng)建定時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 2.添加定時器到runloop中
/**
第一個參數(shù):定時器
第二個參數(shù):runloop的運行模式
*/
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)run {
NSLog(@"run----%@ --- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
-
Tips
定時器在子線程中創(chuàng)建,該定時器不會工作,需要run開啟
主線程RunLoop默認自動創(chuàng)建,子線程的RunLoop需要主動創(chuàng)建
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
......定時器.....
//開啟runloop
[currentRunLoop run];