什么是RunLoop ?
- 可以把RunLoop當(dāng)做是一個無限的死循環(huán),正因為有了這種循環(huán),程序才會在執(zhí)行完一遍后也不會掛掉,如果沒有殺死程序進(jìn)程,程序會一直監(jiān)聽app中的各種事件(比如觸摸事件,定時器事件,selector事件)但不同于死循環(huán)的是RunLoop會在事件監(jiān)聽的過程中處于休眠狀態(tài),該做事時做事,該休息時休息,這樣可以節(jié)省cpu資源,提高程序性能
- 一個線程對應(yīng)一個RunLoop,主線程RunLoop默認(rèn)已經(jīng)開啟,而子線程的RunLoop需要手動開啟(調(diào)用run方法)子線程結(jié)束時RunLoop銷毀
獲取RunLoop對象 Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
獲取RunLoop對象 Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); //獲得主線程的RunLoop對象
RunLoop結(jié)構(gòu)

Paste_Image.png
- Core Foundation中關(guān)于RunLoop的五個類
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef - CFRunLoopModeRef代表RunLoop的運行模式
- 一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
- 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
- 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進(jìn)入
- 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
CFRunLoopModeRef
系統(tǒng)默認(rèn)注冊了5個mode
- NSDefaultRunLoopMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode: 在剛啟動 App 時第進(jìn)入的第一個 Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
- NSRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode,從表面上看這是一個models,也就是一個模式的數(shù)組包含默認(rèn)模式和界面跟鐘模式,實際上他會處理兩種模式下同時產(chǎn)生的各種事件
CFRunLoopTimerRef
CFRunLoopTimerRef是基于時間的觸發(fā)器
CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響
GCD的定時器不受RunLoop的Mode影響
CFRunLoopSourceRef (事件源 輸入源)
按照官方文檔,Source的分類
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
CFRunLoopObserverRef
(CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即將處理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), //即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
給主線程runloop添加觀察者
// 1.創(chuàng)建一個監(jiān)聽對象
/*
第一個參數(shù): 告訴系統(tǒng)如何給Observer對象分配存儲空間
第二個參數(shù): 需要監(jiān)聽的類型
第三個參數(shù): 是否需要重復(fù)監(jiān)聽
第四個參數(shù): 優(yōu)先級
第五個參數(shù): 監(jiān)聽到對應(yīng)的狀態(tài)之后的回調(diào)
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// NSLog(@"%lu", activity);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"進(jìn)入RunLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進(jìn)入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"剛剛從睡眠中醒來");
break;
case kCFRunLoopExit:
NSLog(@"退出RunLoop");
break;
default:
break;
}
});
// 2.給主線程的RunLoop添加監(jiān)聽
/*
第一個參數(shù):需要監(jiān)聽的RunLoop對象
第二個參數(shù):給指定的RunLoop對象添加的監(jiān)聽對象
第三個參數(shù):在那種模式下監(jiān)聽
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
//3.釋放Observer
CFRelease(observer);
RunLoop處理邏輯-官方版

Paste_Image.png
RunLoop處理邏輯-網(wǎng)友整理

Paste_Image.png
常駐線程
應(yīng)用場景:開發(fā)中要頻繁的執(zhí)行某一個耗時操作,通常會把這個耗時的操作放在子線程中執(zhí)行,而系統(tǒng)默認(rèn)子線程中任務(wù)執(zhí)行完后這條子線程就會銷毀,
注意 這里千萬不要以為只要使用全局屬性強(qiáng)引用這條線程就可以繼續(xù)使用這條線程了,
測試代碼: 1/2
- (void)viewDidLoad
{
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
self.thread.name = @"我是常駐線程";
[self.thread start];
}
- (void)demo
{
// 獲取當(dāng)前線程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
// 獲取當(dāng)前runLoop對象
NSRunLoop *runLoop =[NSRunLoop currentRunLoop];
// 以下代碼的目的是為了保證runloop不死 一個NSRunLoop中, 如果沒有source或者timer, 那么NSRunLoop就會退出死循環(huán)
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 運行runLoop對象
[runLoop run];
NSLog(@"如果開啟運行循環(huán) 我不會被打印出來");
}
打印結(jié)果:

Paste_Image.png
測試代碼: 2/2
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 獲取當(dāng)前線程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
// 主線程
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)test
{
// 獲取當(dāng)前線程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
NSLog(@"已確認(rèn)可以繼續(xù)使用這條線程做事");
}
打印結(jié)果
**2016-04-07 14:34:37.858 08-RunLoop****應(yīng)用場景****[3069:184521] <NSThread: 0x7fb60ac08a10>{number = 1, name = main}****--****49**
**2016-04-07 14:34:37.859 08-RunLoop****應(yīng)用場景****[3069:184863] <NSThread: 0x7fb60af4af30>{number = 2, name = ****我是常駐線程****}****--****57**
**2016-04-07 14:34:37.859 08-RunLoop****應(yīng)用場景****[3069:184863] ****已確認(rèn)可以繼續(xù)使用這條線程做事**

Paste_Image.png