runloop 的狀態(tài)待完善
相關(guān)面試題
- 講講 RunLoop,項目中有用到嗎?
1、控制線程的生命周期(線程?;?
2、解決 NSTimer 在滑動時停止工作的問題
3、監(jiān)控應(yīng)用卡幀
4 、性能優(yōu)化 - runloop內(nèi)部實現(xiàn)邏輯?
- runloop和線程的關(guān)系?
線程與 runloop 一一對應(yīng),主線程的 runloop 默認開啟,子線程的 runloop 在第一次獲取的時候開啟,runloop 在線程結(jié)束時銷毀,線程和 runloop 存放在一個全局的字典中,該字典以線程 key,runloop 作為 value. - timer 與 runloop 的關(guān)系?
timer 需要添加到 runloop 中才能工作,runloop 中有一個CFRunLoopTimerRef 類型的事件源專門用來出來 timer 事件. - 程序中添加每3秒響應(yīng)一次的NSTimer,當拖動tableview時timer可能無法響應(yīng)要怎么解決?
將 timer 添加到 runloop 的 commonModel 模式下 - runloop 是怎么響應(yīng)用戶操作的, 具體流程是什么樣的?
- 說說runLoop的幾種狀態(tài)
- runloop的mode作用是什么?
什么是 RunLoop
- RunLoop 顧名思義就是在運行過程中循環(huán)做一些事情
- 應(yīng)用范疇:
1、定時器(Timer)、PerformSelector
2、GCD Async Main Queue
3、事件響應(yīng)、手勢識別、界面刷新
4、網(wǎng)絡(luò)請求
5、AutoreleasePool
RunLoop的作用
1、保持程序的持續(xù)運行
2、處理App中的各種事件(比如觸摸事件、定時器事件等)
3、節(jié)省CPU資源,提高程序性能:該做事時做事,該休息時休息
- 如果沒有 RunLoop 程序執(zhí)行完就會退出,例如下面的代碼,程序在執(zhí)行完main 函數(shù)后就會退出。
int main (int argc, const char * argv[]){
@autoreleasepool{
NSLog(@"hello world");
}
return 0;
}
- 但是我們通??吹降膇os 程序運行在手機上后就一直處在運行狀態(tài)等待用戶的響應(yīng),它是怎么做到的呢?其實是因為ios 應(yīng)用程序在 UIApplicationMain函數(shù)中幫我們開啟了一個 runloop,runloop 保持我們的 ios 程序持續(xù)運行,下面是一個 ios 的 main 函數(shù):
int main(int argc, char *argv[]){
@autoreleasepool{
return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
}
}
- 那么 runLoop 是怎么實現(xiàn)的呢?
下面用偽代碼來實現(xiàn)以下
int main(int argc, char *argc[]){
int retVal = 0;
do{
// 睡眠中等待消息
int message = sleep_and_wait();
// 處理消息,處理完消息返回 0,繼續(xù) do...while 循環(huán)
retVal = process_message(message);
} while(0 == retVal);
return 0;
}
RunLoop 對象
- iOS中有2套API來訪問和使用RunLoop
1、Foundation:NSRunLoop
2、Core Foundation:CFRunLoopRef - NSRunLoop和CFRunLoopRef都代表著RunLoop對象
- NSRunLoop是基于CFRunLoopRef的一層OC包裝
- CFRunLoopRef是開源的
https://opensource.apple.com/tarballs/CF/
RunLoop 與線程
- 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建
- RunLoop會在線程結(jié)束時銷毀
1、獲取 Runloop的方式:
[NSRunLoop currentRunLoop];獲取當前線程的 RunLoop 對象
[NSRunLoop mainRunLoop];獲取主線程的 RunLoop 對象
或者
CFRunLoopGetCurrent();獲得當前線程的 RunLoop 對象
CFRunLoopGetMain()獲得主線程的 Runloop 對象 - 下面我們通過源碼來驗證上面的結(jié)論:
由于 CFRunLoop 是開源的我們可以在源碼中找到CFRunLoopGetCurrent函數(shù)
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
上面我們看到其返回的是一個_CFRunLoopGet0函數(shù)
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 這里我們可以看到 loop 對象是從CFDictionaryGetValue這個字典中取出來的,而且是以線程指針作為 key,runloop 對象作為 value 的,
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 這里如果從字典中去到的loop 對象是空,那么就會創(chuàng)建一個新的線程對象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 這里將新的 newLoop 對象賦值給 loop,并保存在字典中
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;
}
RunLoop 相關(guān)的類
- Core Foundation中關(guān)于RunLoop的5個類
CFRunLoopRef :
-
CFRunLoopRef其實是一個__CFRunLoopRef類型的結(jié)構(gòu)體,其有兩個比較中要的變量CFRunLoopModeRef _currentMode; CFMutableSetRef _modes;
_currentMode存放的是CFRunLoopModeRef類型的當前mode
_modes存放的是當前RunLoop對象中所有的CFRunLoopModeRef類型的mode集合
_commonModes中存放的是所有標記為 commonModel 的集合,例如kCFRunLoopDefaultMode、UITrackingRunLoopMode
_commonModeItems中存放的是所有添加到kCFRunLoopCommonModes的所有對象,
源碼如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
其結(jié)構(gòu)如下:

CFRunLoopModeRef
- 它其實是一個
__CFRunLoopMode類型的結(jié)構(gòu)體,其有四個比較重要的變量CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers;
_sources0和_sources1中存放的是CFRunLoopSourceRef類型的變量;
_observers中存放的是CFRunLoopObserverRef類型的變量;
_timers中存放的是CFRunLoopTimerRef類型的變量;
源碼如下:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
- CFRunLoopModeRef 代表RunLoop的運行模式,一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
,RunLoop啟動時只能選擇其中一個Mode,作為currentMode,如果需要切換Mode,只能退出當前Loop,再重新選擇一個Mode進入,不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響,如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出。
目前已知的Mode有5種
- kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
- UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
- UIInitializationRunLoopMode:在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到.
- kCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode.添加到該模式下的事件源可以在所有標記為 commonMode的mode(kCFRunLoopDefaultMode、UITrackingRunLoopMode) 下運行
CFRunLoopSourceRef
- _modes中的_sources0和_sources1的類型;
- _sources0主要處理,觸摸時間和performSelector:onThread:類型的事件
下面我們用源碼驗證一下,首先驗證觸摸事件的
我們新建一個viewController中添加一個touchesBegan:方法,并在NSLog處打上斷點,
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touchesBegan");
}
點擊屏幕觸發(fā)斷點,然后我們在控制臺執(zhí)行bt,得到函數(shù)調(diào)用棧信息如下:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000105516efd RunLoop`-[ViewController touchesBegan:withEvent:](self=0x00007f8b9e705a60, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600001884d80) at ViewController.m:25:5
frame #1: 0x00007fff48bf59c6 UIKitCore`forwardTouchMethod + 323
frame #2: 0x00007fff48bf5872 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x00007fff48c0478f UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
frame #4: 0x00007fff48c067f5 UIKitCore`-[UIWindow sendEvent:] + 4501
frame #5: 0x00007fff48be0c39 UIKitCore`-[UIApplication sendEvent:] + 356
frame #6: 0x00007fff48c6b096 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7328
frame #7: 0x00007fff48c6e262 UIKitCore`__handleEventQueueInternal + 6565
frame #8: 0x00007fff48c64dcb UIKitCore`__handleHIDEventFetcherDrain + 88
frame #9: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #10: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
frame #11: 0x00007fff23d9d5b4 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #12: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
frame #13: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #14: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #15: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #16: 0x00000001055171c2 RunLoop`main(argc=1, argv=0x00007ffeea6e7cb8) at main.m:18:12
frame #17: 0x00007fff519521fd libdyld.dylib`start + 1
frame #18: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
在frame #9中我們看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 可以驗證該事件源來自source0。
下面我們驗證performSelector:onThread:類型的,在viewDidLoad方法中加入如下代碼,并在test方法內(nèi)打上斷點
dispatch_async(dispatch_get_global_queue(0,0), ^{
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:true];
});
-(void)test{
NSLog(@"test");
}
觸發(fā)斷點,在控制臺執(zhí)行bt,得到如下函數(shù)堆棧信息:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102a14e27 RunLoop`-[ViewController test](self=0x00007fcaab50b2e0, _cmd="test") at ViewController.m:28:5
frame #1: 0x00007fff2593af42 Foundation`__NSThreadPerformPerform + 209
frame #2: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #3: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
frame #4: 0x00007fff23d9d60c CoreFoundation`__CFRunLoopDoSources0 + 268
frame #5: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #9: 0x0000000102a15152 RunLoop`main(argc=1, argv=0x00007ffeed1e9cb8) at main.m:18:12
frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
從frame #2中可以看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__驗證是source0。
那么source1是干嘛的呢?source1主要是用來處理基于Port的線程間通信 和 系統(tǒng)事件捕捉的
source0 和 source1 有什么區(qū)別呢?
Source1 :基于mach_Port的,來自系統(tǒng)內(nèi)核或者其他進程或線程的事件,可以主動喚醒休眠中的RunLoop(iOS里進程間通信開發(fā)過程中我們一般不主動使用)。mach_port大家就理解成進程間相互發(fā)送消息的一種機制就好, 比如屏幕點擊, 網(wǎng)絡(luò)數(shù)據(jù)的傳輸都會觸發(fā)sourse1。Source0 :非基于Port的 處理事件,什么叫非基于Port的呢?就是說你這個消息不是其他進程或者內(nèi)核直接發(fā)送給你的。一般是APP內(nèi)部的事件, 比如hitTest:withEvent的處理, performSelectors的事件。簡單舉個例子:一個APP在前臺靜止著,此時,用戶用手指點擊了一下APP界面,那么過程就是下面這樣的:
我們觸摸屏幕,先摸到硬件(屏幕),屏幕表面的事件會被IOKit先包裝成Event,通過mach_Port傳給正在活躍的APP , Event先告訴source1(mach_port),source1喚醒RunLoop, 然后將事件Event分發(fā)給source0,然后由source0來處理。
如果沒有事件,也沒有timer,則runloop就會睡眠, 如果有,則runloop就會被喚醒,然后跑一圈。
我們可以這樣理解source1是系統(tǒng)事件,source0是應(yīng)用層事件,source1接收到事件后將事件分發(fā)給source0來處理
CFRunLoopTimerRef
- _modes中的_timers的類型,主要處理定時器事件
我們在viewDidLoad中添加下面源碼,并在test方法內(nèi)打上斷點,
[NSTimer scheduledTimerWithTimeInterval:3 repeats:true block:^(NSTimer * _Nonnull timer) {
[self test];
}];
觸發(fā)斷點,在終端中執(zhí)行bt,得到如下信息:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x0000000102b44e27 RunLoop`-[ViewController test](self=0x00007fcc0bc0a490, _cmd="test") at ViewController.m:32:5
frame #1: 0x0000000102b44d7b RunLoop`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x00006000018c02d0, timer=0x00006000023a8900) at ViewController.m:25:9
frame #2: 0x00007fff2594462e Foundation`__NSFireTimer + 72
frame #3: 0x00007fff23d9e634 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
frame #4: 0x00007fff23d9e2ce CoreFoundation`__CFRunLoopDoTimer + 1038
frame #5: 0x00007fff23d9d92a CoreFoundation`__CFRunLoopDoTimers + 282
frame #6: 0x00007fff23d9857e CoreFoundation`__CFRunLoopRun + 1950
frame #7: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #8: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #9: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #10: 0x0000000102b45152 RunLoop`main(argc=1, argv=0x00007ffeed0b9cb8) at main.m:18:12
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
frame #12: 0x00007fff519521fd libdyld.dylib`start + 1
(lldb)
在frame #3:行中我們看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__驗證是處理定時器事件的。
CFRunLoopObserverRef
- 用來監(jiān)聽RunLoop的狀態(tài)
- 對應(yīng)狀態(tài)如下:

RunLoop是怎樣的一個循環(huán)
- 流程如下:
01、通知Observers:進入Loop
02、通知Observers:即將處理Timers
03、通知Observers:即將處理Sources
04、處理Blocks
05、處理Source0(可能會再次處理Blocks)
06、如果存在Source1,就跳轉(zhuǎn)到第8步
07、通知Observers:開始休眠(等待消息喚醒)
08、通知Observers:結(jié)束休眠(被某個消息喚醒)
01> 處理Timer
02> 處理GCD Async To Main Queue
03> 處理Source1
09、處理Blocks
10、根據(jù)前面的執(zhí)行結(jié)果,決定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop

- 上面我們在執(zhí)行斷點時在終端使用bt查看函數(shù)調(diào)用棧的時候都會看到CoreFoundation __CFRunLoopRun這個函數(shù),其實這個就是Runloop啟動的入口。在源碼CFRunLoop.c中可以找到CFRunLoopRun函數(shù),源碼如下:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
可以看到源碼中就調(diào)用了CFRunLoopRunSpecific這個函數(shù),源碼如下:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
// 通知Observers:進入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 進行runloop 的核心邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出runloop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
通過源碼看到返回的result是通過__CFRunLoopRun函數(shù)獲取的,__CFRunLoopRun函數(shù)源碼比較多,下面是提取出來的核心代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
// 先判斷是否是stopped狀態(tài),如果是返回stopped的狀態(tài)
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
int32_t retVal = 0;
do {
// 通知Observers 即將處理Timers
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers 即將處理Sources
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理Blocks,是NSRunLoop 中的 performBlock:函數(shù)中的block
__CFRunLoopDoBlocks(rl, rlm);
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {、
// 處理Blocks,是NSRunLoop 中的 performBlock:函數(shù)中的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//如果有Sources1,就跳轉(zhuǎn)打handle_msg標記處
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}
// 通知Observers即將休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 進入休眠,等待其他消息喚醒
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
// 休眠后進去do...while(1)的死循環(huán),循環(huán)里等待喚醒的消息,如果被喚醒執(zhí)行break跳出死循環(huán)
do {
if (kCFUseCollectableAllocator) {
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
break;
}
} while (1);
// 醒來
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers 已經(jīng)被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被timer喚醒,處理Timer
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
else if (livePort == dispatchPort) { // 被GCD 喚醒,處理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被sources1喚醒,處理Sources1
if (rls) {
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 執(zhí)行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根據(jù)之前的執(zhí)行結(jié)果,來決定怎么做,如果retVal被標記為非零,當前循環(huán)結(jié)束,返回retVal,CFRunLoopRunSpecific中通知observers退出runloop,如果是Stopped或Finished則外層CFRunLoopRun中的do...while循環(huán)結(jié)束,代碼執(zhí)行完畢,線程結(jié)束
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
- GCD 如果是異步回到主線程的話是由runloop處理的
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"111-----%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"222-----%@",[NSThread currentThread]);
});
});
在NSLog(@"222-----%@",[NSThread currentThread]);處斷點,出發(fā)斷點后,終端執(zhí)行bt獲得函數(shù)調(diào)用棧如下
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x000000010f21dd70 RunLoop`__29-[ViewController viewDidLoad]_block_invoke_2(.block_descriptor=0x000000010f2200a0) at ViewController.m:31:33
frame #1: 0x000000010f487f11 libdispatch.dylib`_dispatch_call_block_and_release + 12
frame #2: 0x000000010f488e8e libdispatch.dylib`_dispatch_client_callout + 8
frame #3: 0x000000010f496d97 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1149
frame #4: 0x00007fff23d9da89 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
frame #5: 0x00007fff23d985d9 CoreFoundation`__CFRunLoopRun + 2041
frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
frame #9: 0x000000010f21e102 RunLoop`main(argc=1, argv=0x00007ffee09e1cb8) at main.m:18:12
frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
通過函數(shù)調(diào)用??吹狡涫峭ㄟ^CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE函數(shù)來處理的

線程保活
線程一旦執(zhí)行完線程內(nèi)的任務(wù)就會死亡,如果不想讓線程在執(zhí)行完任務(wù)之后死亡,就需要給線程的 runloop 的 mode 中添加source0、source1、timer、observer如果 runloop 中有四個中的任意一種就不會退出,runloop 不退出線程內(nèi)的任務(wù)就沒執(zhí)行完,就不會銷毀,我們可以給 runloop 添加一種事件源,讓其不退出
@interface ZWThread : NSThread
@end
@implementation ZWThread
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
@interface ViewController ()
@property (strong, nonatomic) ZWThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[ZWThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer, 這樣 runloop 不會立即退出
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
// 這里運行 runloop,其實是"阻塞"線程,當 runloop 接收到事件時去處理事件,處理完了之后就休眠,當收到下次事件時重新判斷 while 的條件是否成立,如果成立,繼續(xù)執(zhí)行循環(huán)體,如果不成立跳出 while 循環(huán)
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// 只有當 runloop stop 的時候才會走到這里打印,沒有 stop 的話,就一直在 while 循環(huán)體內(nèi)處理事件和休眠.
NSLog(@"%@----end----", [NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// if (!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子線程需要執(zhí)行的任務(wù)
- (void)test{
NSLog(@"after %s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
if (!self.thread) return;
// 在子線程調(diào)用stop(waitUntilDone設(shè)置為YES,代表子線程的代碼執(zhí)行完畢后,這個方法才會往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子線程的RunLoop
- (void)stopThread{
// 設(shè)置標記為YES
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
// 清空線程
self.thread = nil;
}
- (void)dealloc{
NSLog(@"%s", __func__);
[self stop];
}