RunLoop深入了解及常駐線程組件開發(fā)

一、什么是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ā)的)。


用戶態(tài)與內(nèi)核態(tài)的切換流程如上圖

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


核心函數(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)建。


獲取RunLoop對象的底層源碼

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容