一、什么是RunLoop
1、概念:運(yùn)行循環(huán),在程序運(yùn)行過程中,循環(huán)的做一些事,實(shí)質(zhì)就是一個(gè)do while()循環(huán)。
2、應(yīng)用范疇:NSTimer、perfermSelector、GCDAsyncMainQueue、事件響應(yīng)、手勢識(shí)別、UI界面刷新、網(wǎng)絡(luò)請求、AutoreleasePool等。
3、如果沒有RunLoop我們的App是怎么樣呢?如果沒有RunLoop我的app程序在main函數(shù)執(zhí)行完log就即將退出程序。

4、如果有RunLoop,則程序不會(huì)立即退出,而是保持運(yùn)行狀態(tài)。

5、RunLoop的作用就是:
? ? ?1)保持程序的持續(xù)運(yùn)行
? ???2)處理app中的各種事件,比如觸摸事件,Timer計(jì)時(shí)器等。
? ???3)可以節(jié)省CPU資源,提高程序性能,有事做就做事,無事做就休眠。(休眠:mach_msg函數(shù),切換到內(nèi)核態(tài))
二、RunLoop休眠原理簡介
1、RunLoop最核心的事情:保證線程在沒有消息時(shí)休眠以避免占用系統(tǒng)資源,有消息時(shí)能夠及時(shí)喚醒。通過用戶態(tài)與內(nèi)核態(tài)的切換來實(shí)現(xiàn)休眠。RunLoop的這個(gè)機(jī)制是依靠系統(tǒng)內(nèi)核來完成的,具體來說是蘋果操作系統(tǒng)核心組件Darwin中的Mach來完成的(Darwin是開源的:https://opensource.apple.com/tarballs/可以里面找下載了解)。
2、Mach是內(nèi)核的核心,提供了進(jìn)程間通信(IPC)、處理器調(diào)度等基礎(chǔ)服務(wù)。在Mach中,進(jìn)程、線程間的通信是以消息的方式來完成的,消息在兩個(gè)Port之間進(jìn)行傳遞(Source1事件就是依靠系統(tǒng)發(fā)送消息到指定的Port來觸發(fā)的)。
用

3、使RunLoop休眠的重要函數(shù)

mach_msg()會(huì)觸發(fā)內(nèi)核狀態(tài)切換。當(dāng)程序靜止時(shí),RunLoop停留在__CFRunLoopServiceMachPort(waitSet, &msg,sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy),而這個(gè)函數(shù)內(nèi)部就是調(diào)用了mach_msg()讓程序處于休眠狀態(tài)。
三、RunLoop的API
1、Ios有兩套API來訪問使用RunLoop
--Foundation:NSRunLoop
? ?--CoreFoundation:CFRunLoopRef
? ?NSRunLoop是基于CFRunLoopRef的一層OC包裝。
? ?CFRunLoopRef開源源碼下載:https://opensource.apple.com/tarballs/CF/
四、RunLoop與線程
1、RunLoop與線程是一對一的關(guān)系。每條線程可以有一個(gè)與之對應(yīng)的RunLoop對象。
2、RunLoop保存在一個(gè)全局的Dictionary里,線程作為key,RunLoop作為value。
3、線程剛創(chuàng)建時(shí)并沒有RunLoop對象,RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建。

4、RunLoop會(huì)在線程結(jié)束時(shí)銷毀。
5、主線程的RunLoop已經(jīng)自動(dòng)獲?。▌?chuàng)建),子線程默認(rèn)沒有開啟RunLoop。
五、RunLoop對象的獲取

六、RunLoop相關(guān)類
Core Foundation中關(guān)于RunLoop的5個(gè)
1、CFRunLoopRef
2、CFRunLoopModeRef
3、CFRunLoopSourceRef
4、CFRunLoopTimerRef
5、CFRunLoopObserverRef
七、RunLoop、RunLoopMode結(jié)構(gòu)組成
1、RunLoop結(jié)構(gòu)

RunLoop在CF層結(jié)構(gòu)體組成,有線程指針,mode集合等。
問題:既然RunLoop與線程是一對一關(guān)系,那么此處RunLoop里面有_pthread_t線程指針,是否構(gòu)成相互引用呢??
答案:不會(huì),因?yàn)長oop對象與線程只是value與key的對應(yīng)關(guān)系,不存在相互持有。
2、RunLoopMode結(jié)構(gòu)

1)RunLoopMode代表RunLoop的運(yùn)行模式
2)一個(gè)RunLoop包含若干個(gè)Mode,每個(gè)Mode又包含若干個(gè)Source0/Source1/Timer/Observer
3)RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode,當(dāng)前運(yùn)行的模式。
4)如果需要切換Mode,只能退出當(dāng)前Loop,再重新選擇一個(gè)Mode進(jìn)入。
5)不同Mode下的Source0/Source1/Timer/Observer能分隔開來,互不影響。
6)如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會(huì)休眠或者立馬退出。
八、RunLoop的CurrentMode如何獲取
CFRunLoopCopyCurrentMode(CFRunLoopRefrl);獲取當(dāng)前RunLoop運(yùn)行的Mode的Name

九、RunLoopMode的5種類
1、kCFRynLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個(gè)Mode下運(yùn)行。
2、UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響。
3、kCFRunLoopCommonModes:這是一個(gè)占位用的Mode,不是一種真正的Mode。帶 有 common modes 標(biāo) 記 的 模 式 有 UITrackingRunLoopMode和 kCFRunLoopDefaultMode。
4、UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后不再使用。
5、GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到。
說明:主要我們時(shí)常用到的就是kCFRynLoopDefaultMode與UITrackingRunLoopMode
十、RunLoop運(yùn)行邏輯



邏輯順序:
(1)doSource0
---觸摸事件處理
---pperformSelector:/onThread:
(2)doSource1
--基于Port的線程間通信
--系統(tǒng)事件捕捉
(3)doTimers
--NSTimer
--performSelector:withObject:afterDelay:
(4)doObservers
--用于監(jiān)聽RunLoop的狀態(tài)
--UI刷新(BeforeWaiting)
--Autoreleasepool(BeforeWaiting)
1、RunLoop只能運(yùn)行在其中一個(gè)mode模式下,如果要運(yùn)行另外的mode,需要退出當(dāng)前mode然后再次進(jìn)入需要運(yùn)行的mode。
2、每個(gè)mode里面有自己獨(dú)立的source0/source1/observers/timers。運(yùn)行在某個(gè)mode下只能處理mode里面對應(yīng)的事件源。
十一、CFRunLoopObserverRef與RunLoopActivity狀態(tài)

1、CFRunLoopObserver創(chuàng)建的2種方式
?CFRunLoopObserverCreate
?CFRunLoopObserverCreateWithHandler
2、CFRunLoopObserverCreate注意點(diǎn)
??函數(shù):CFRunLoopObserverCreate(CFAllocatorRefallocator, CFOptionFlagsactivities, Boolean repeats, CFIndexorder, CFRunLoopObserverCallBackcallout, CFRunLoopObserverContext*context) ;
其中的context最好不要為NULL,如果為空,可能出現(xiàn)莫名的報(bào)錯(cuò),最好做個(gè)上下文初始化。
CFRunLoopObserverContext context={0};//結(jié)構(gòu)體初始化
因?yàn)楹瘮?shù)局部變量如果沒有初始化,可能存放的是一些亂七八糟的初始值,有可能當(dāng)前的函數(shù)調(diào)用棧的空間是上個(gè)函數(shù)遺留的。例如:

3、CFRunLoopObserverCreateWithHandler使用

4、CFRunLoopRunResult結(jié)構(gòu)

十二、RunLoop在實(shí)際開中的應(yīng)用
1、控制線程生命周期(線程保活)
2、解決NSTimer在滑動(dòng)時(shí)停止工作的問題
3、監(jiān)控應(yīng)用卡頓
4、性能優(yōu)化
十三、利用RunLoop技術(shù)創(chuàng)建一個(gè)可控生命周期常駐線程。
1、大體創(chuàng)建常駐線程及停止的代碼核心如下:
NSRunLoop* loop = [NSRunLoopcurrentRunLoop];
[loopaddPort:[NSPortport] forMode:NSDefaultRunLoopMode];
[loop run];//開啟runLoop
CFRunLoopStop(loop);//停止Loop,沒有NS開頭相關(guān)的Foundation下的API,只有CF層的API,CFRunLoopStop函數(shù)使得loop停止。
上面代碼存在的問題:
1、為什么不加[loop addPort:[NSPortport] forMode:NSDefaultRunLoopMode];直接[loop run]不會(huì)啟動(dòng)loop而是直接退出?

? ?[loop run]的底層核心代碼就是執(zhí)行上面的CFRunLoopRunSpecific,如果沒有加入port端口等事件源,那么__CFRunLoopFindMode找到的currentMode=NULL,所有直接就return退出了,不會(huì)啟動(dòng)runLoop。因此如果要構(gòu)建一個(gè)常住線程啟動(dòng)runLoop,必須加入事件源才能run起來。
2、[runLoop run];跑起的RunLoop為什么CFRunLoopStop()停止不了?

??上面運(yùn)行截圖看出,loopStop只是停止了一次loop,然后里面loop又run起來了,說明[runLoop run]根本沒停掉。那么[runLoop run]到底怎么回事呢?看下嘛文檔截圖說明:

從文檔可看出開啟的是一個(gè)無限循環(huán)。不停的調(diào)用runMode:beforeDate方法開啟loop。所以當(dāng)我們調(diào)用__CFRunloopStop()的時(shí)候只是停止了其中一次loop。然后由于開啟的是無限調(diào)用runMode:beforeDate方法,從而又開啟了loop。相當(dāng)于一個(gè)do{}whlie(YES)死循環(huán)。
從GNUStep里面下載Foundation源碼佐證:
源碼下載地址:http://wwwmain.gnustep.org/resources/downloads.php?site=ftp%3A%2F%2Fftp.gnustep.org%2Fpub%2Fgnustep%2F
run方法其實(shí)調(diào)用的是runUntilDate:theFuture,theFuture是[NSDate distantFuture]


總結(jié):要開啟一個(gè)可控的loop生命周期,就不能使用run()來開啟loop,而是要用runMode:beforeDate方法。我做了個(gè)Demo(https://github.com/harrywater/ThreadKeepAlive.git)如果利用CF層的CFRunLoopRun()函數(shù)來啟動(dòng),則不會(huì)出現(xiàn)這種情況,
可以達(dá)到目的。這也是兩則的區(qū)別,也就是說[NSRunLooprun]內(nèi)部實(shí)現(xiàn)是跟CFRunLoopRun沒有關(guān)系,相互獨(dú)立的,雖然很多NSRunLoop的方法底層核心就是對應(yīng)的CF層相應(yīng)函數(shù)。也可以使用CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);啟動(dòng)一個(gè)可控生命周期的常住線程。
3、[runLooprun]與CFRunLoopRun()有區(qū)別嗎?
[NSRunLooprun]內(nèi)部實(shí)現(xiàn)是跟CFRunLoopRun沒有關(guān)系,相互獨(dú)立的,雖然很多NSRunLoop的方法底層核心就是對應(yīng)的CF層相應(yīng)函數(shù)。也可以使用CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);啟動(dòng)一個(gè)可控生命周期的常住線程。
4、__CFRunLoop結(jié)構(gòu)體里面的_modes表示的是loop運(yùn)行的mode集合,那么這個(gè)set里面的元素個(gè)數(shù)會(huì)隨著addSource:forMode:的方法加入事件源,相應(yīng)增加嗎?這個(gè)集合的count數(shù)是多少?

利用CFRunLoopCopyAllModes()函數(shù)得到主線程所有的Modes打印。在app內(nèi)操作,不管我們?nèi)绾位瑒?dòng)scrolView或者其他事件加入處理,主線程的RunLoopMode打印結(jié)果,可以看出modes里面的Mode并不是隨著Mode切換及加入新的事件源而增加多個(gè)mode。而是在固定的幾個(gè)mode下面運(yùn)行。事件源也是加入在這固定的幾個(gè)mode里面。
十四、構(gòu)建一個(gè)常駐可控生命周期的線程組件
核心代碼就下面這個(gè):
HPAliveThread.h文件
/**可控生命的線程 **/
#import
@interface HPAliveThread : NSObject
//處理線程任務(wù)
- (void)doTask:(void(^)(void))task;
//停止
- (void)stop;
@end
HPAliveThread.m文件
#import "HPAliveThread.h"
@interface HPAliveThread()
@property(nonatomic,strong)NSThread* thread;
//@property(nonatomic,assign)BOOL isStoped;
@end
@implementation HPAliveThread
//觀察runLoop狀態(tài)
void abserverRunLoopActivityFun(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
- (instancetype)init
{
self = [super init];
if (self) {
//? ?? ???self.isStoped = NO;
//? ?? ???__weak typeof(self)weakSelf = self;
_thread = [[NSThread alloc]initWithBlock:^{
//創(chuàng)建一個(gè)觀察者
CFRunLoopObserverContext observerContext = {0};
CFRunLoopObserverRef abserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopEntry|kCFRunLoopExit, YES, 0, abserverRunLoopActivityFun, &observerContext);
//添加觀察者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), abserver, kCFRunLoopDefaultMode);
CFRelease(abserver);
//開啟runLoop??這種方式需要配合一個(gè)外部isStop及do while(...)來做停止跟開啟RunLoop
//? ?? ?? ?? ?NSRunLoop* loop = [NSRunLoop currentRunLoop];
//? ?? ?? ?? ?[loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//
//? ?? ?? ?? ?while (weakSelf && !weakSelf.isStoped) {
//? ?? ?? ?? ?? ? [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//? ?? ?? ?? ?}
//===================推薦使用===============
//簡易代碼 開啟runLoop
//不能為NULL,需要初始化,如果不初始化可能由于函數(shù)調(diào)用??臻g是之前其他函數(shù)留下,可能會(huì)存一些糟數(shù)據(jù),如果為NULL也可能出現(xiàn)報(bào)錯(cuò)
CFRunLoopSourceContext sourceContext??={0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRunLoopRun();
//CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
//========================================
}];
}
return self;
}
#pragma mark --public method
- (void)doTask:(void(^)(void))task
{
if (!self.thread.executing && task) {
[self.thread start];
}
[self performSelector:@selector(__innerThreadTask:) onThread:self.thread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.thread) return;
[self performSelector:@selector(__stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s",__func__);
[self stop];//銷毀線程
}
#pragma mark --private method
- (void)__stop
{
//? ? self.isStoped = YES;
//停止runLoop 線程?;罱Y(jié)束
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
}
- (void)__innerThreadTask:(void(^)(void))task
{
//執(zhí)行task
task();
}
@end
構(gòu)造一個(gè)HPAliveThread類,繼承NSObject,然后里面包含一個(gè)NSThread對象,在這里說下為什么HPAliveThread不繼承NSThread呢?因?yàn)槿绻^承NSThread,很多父類的公有方法都會(huì)可以使用,這個(gè)增加了很多HPAliveThread操作的不確定性,所以讓其繼承NSObject,只給外界提供我想提供的方法即可。
常住可控生命周期線程組件下載地址:https://github.com/harrywater/HPAliveThread.git