一. RunLoop簡介
運行循環(huán),在程序運行過程中循環(huán)做一些事情,如果沒有Runloop程序執(zhí)行完畢就會立即退出,如果有Runloop程序會一直運行,并且時時刻刻在等待用戶的輸入操作。RunLoop可以在需要的時候自己跑起來運行,在沒有操作的時候就停下來休息。充分節(jié)省CPU資源,提高程序性能。
應(yīng)用場景
- 定時器(Timer)------- timer
- PerformSelector ---------- source0
- GCD Async Main Queue
- 只有 dispatch_async(dispatch_get_main_queue) 情況下是通過 runloop 去調(diào)度的
- 事件響應(yīng)、手勢識別、界面刷新
- 網(wǎng)絡(luò)請求
- AutoreleasePool
二. RunLoop基本作用
- 保持程序持續(xù)運行,程序一啟動就會開一個主線程,主線程一開起來就會跑一個主線程對應(yīng)的RunLoop,RunLoop保證主線程不會被銷毀,也就保證了程序的持續(xù)運行
- 處理App中的各種事件(比如:觸摸事件,定時器事件,Selector事件等)
- 節(jié)省CPU資源,提高程序性能,程序運行起來時,當(dāng)什么操作都沒有做的時候,RunLoop就告訴CPU,現(xiàn)在沒有事情做,我要去休息,這時CPU就會將其資源釋放出來去做其他的事情,當(dāng)有事情做的時候RunLoop就會立馬起來去做事情
我們先通過API內(nèi)一張圖片來簡單看一下RunLoop內(nèi)部運行原理

通過圖片可以看出,RunLoop在跑圈過程中,當(dāng)接收到Input sources 或者 Timer sources時就會交給對應(yīng)的處理方去處理。當(dāng)沒有事件消息傳入的時候,RunLoop就休息了
三. 主線程的 RunLoop
UIApplicationMain函數(shù)內(nèi)啟動了Runloop,程序不會馬上退出,而是保持運行狀態(tài)。因此每一個應(yīng)用必須要有一個runloop,
我們知道主線程一開起來,就會跑一個和主線程對應(yīng)的RunLoop,那么RunLoop一定是在程序的入口main函數(shù)中開啟。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在UIApplicationMain函數(shù)中,開啟了一個和主線程相關(guān)的RunLoop,導(dǎo)致UIApplicationMain不會返回,一直在運行中,也就保證了程序的持續(xù)運行。
Runloop 源碼:
// 用DefaultMode啟動
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
RunLoop確實是do while通過判斷result的值實現(xiàn)的。因此,我們可以把RunLoop看成一個死循環(huán)。
四. RunLoop對象
- Fundation框架 (基于CFRunLoopRef的封裝) NSRunLoop對象
- CoreFoundation CFRunLoopRef對象
獲取 Runloop 對象
Foundation
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
Core Foundation
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
CFRunLoopRef 開源代碼:https://opensource.apple.com/tarballs/CF/
五. RunLoop和線程間的關(guān)系
- 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
- RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
- 主線程的RunLoop已經(jīng)自動創(chuàng)建好了,子線程的RunLoop需要主動創(chuàng)建
- RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀
相關(guān)源碼
// 拿到當(dāng)前Runloop 調(diào)用_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
// 查看_CFRunLoopGet0方法內(nèi)部
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);
// 根據(jù)傳入的主線程獲取主線程對應(yīng)的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主線程 將主線程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 從字典里面拿,將線程作為key從字典里獲取一個loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果loop為空,則創(chuàng)建一個新的loop,所以runloop會在第一次獲取的時候創(chuàng)建
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 創(chuàng)建好之后,以線程為key runloop為value,一對一存儲在字典中,下次獲取的時候,則直接返回字典內(nèi)的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;
}
從上面的代碼可以看出,線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個 Dictionary 里。所以我們創(chuàng)建子線程RunLoop時,只需在子線程中獲取當(dāng)前線程的RunLoop對象即可[NSRunLoop currentRunLoop];如果不獲取,那子線程就不會創(chuàng)建與之相關(guān)聯(lián)的RunLoop,并且只能在一個線程的內(nèi)部獲取其 RunLoop
[NSRunLoop currentRunLoop];方法調(diào)用時,會先看一下字典里有沒有存子線程相對用的RunLoop,如果有則直接返回RunLoop,如果沒有則會創(chuàng)建一個,并將與之對應(yīng)的子線程存入字典中。當(dāng)線程結(jié)束時,RunLoop會被銷毀。
從源碼看出子線程只有第一次獲取 runloop 時才會創(chuàng)建

六. RunLoop結(jié)構(gòu)體

__CFRunLoop結(jié)構(gòu)體 源碼
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;
};
主要成員變量
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
CFRunLoopModeRef 其實是指向__CFRunLoopMode結(jié)構(gòu)體的指針,__CFRunLoopMode結(jié)構(gòu)體源碼如下
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 */
};
主要成員變量
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
通過上面分析我們知道,CFRunLoopModeRef代表RunLoop的運行模式,一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer,而RunLoop啟動時只能選擇其中一個Mode作為currentMode。
Source1/Source0/Timers/Observer分別代表什么
- Source1 : 基于Port的線程間通信
- Source0 : 觸摸事件,PerformSelectors
- Timers : 定時器,NSTimer
- Observer : 監(jiān)聽器,用于監(jiān)聽RunLoop的狀態(tài)
觸摸事件屬于 source1 還是 source0
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"點擊了屏幕");
}
打斷點之后打印堆棧信息,當(dāng)xcode工具區(qū)打印的堆棧信息不全時,可以在控制臺通過“bt”指令打印完整的堆棧信息,由堆棧信息中可以發(fā)現(xiàn),觸摸事件確實是會觸發(fā)Source0事件。

performSelector 屬于 source1 還是 source0
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
});
可以發(fā)現(xiàn)PerformSelectors同樣是觸發(fā)Source0事件

當(dāng)我們觸發(fā)了事件(觸摸/鎖屏/搖晃等)后,由IOKit.framework生成一個 IOHIDEvent事件,而IOKit是蘋果的硬件驅(qū)動框架,由它進(jìn)行底層接口的抽象封裝與系統(tǒng)進(jìn)行交互傳遞硬件感應(yīng)的事件,并專門處理用戶交互設(shè)備,由IOHIDServices和IOHIDDisplays兩部分組成,其中IOHIDServices是專門處理用戶交互的,它會將事件封裝成IOHIDEvents對象,接著用mach port轉(zhuǎn)發(fā)給需要的App進(jìn)程,隨后 Source1就會接收IOHIDEvent,之后再回調(diào)__IOHIDEventSystemClientQueueCallback(),__IOHIDEventSystemClientQueueCallback()內(nèi)觸發(fā)Source0,Source0 再觸發(fā) _UIApplicationHandleEventQueue()。所以觸摸事件看到是在 Source0 內(nèi)的。
總結(jié):觸摸事件先通過 mach port 發(fā)送,封裝為 source1,之后又轉(zhuǎn)換為 source0
Timers 驗證
[NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"NSTimer ---- timer調(diào)用了");
}];

Observer

七. 詳解RunLoop相關(guān)類及作用
相關(guān)類
- CFRunLoopRef - 獲得當(dāng)前RunLoop和主RunLoop
- CFRunLoopModeRef - RunLoop 運行模式,只能選擇一種,在不同模式中做不同的操作
- CFRunLoopSourceRef - 事件源,輸入源
- CFRunLoopTimerRef - 定時器時間
- CFRunLoopObserverRef - 觀察者
1. CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的運行模式
一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source、Timer、Observer
每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
如果需要切換Mode,只能退出當(dāng)前一次 Loop,再重新指定一個Mode進(jìn)入,這樣做主要是為了分隔開不同組的Source、Timer、Observer,讓其互不影響。如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出

注意:一種Mode中可以有多個Source(事件源,輸入源,基于端口事件源例鍵盤觸摸等) Observer(觀察者,觀察當(dāng)前RunLoop運行狀態(tài)) 和Timer(定時器事件源)。但是必須至少有一個Source或者Timer,因為如果Mode為空,RunLoop運行到空模式不會進(jìn)行空轉(zhuǎn),就會立刻退出。
系統(tǒng)默認(rèn)注冊的5個Mode:
RunLoop 有五種運行模式,其中常見的有1.2兩種
1. kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運行
2. UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
3. UIInitializationRunLoopMode: 在剛啟動 App 時第進(jìn)入的第一個 Mode,啟動完成后就不再使用,會切換到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到
5. kCFRunLoopCommonModes: 這是一個占位用的Mode,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode
2. CFRunLoopSourceRef事件源(輸入源)
source 分為兩種
- Source0:非基于Port的 用于用戶主動觸發(fā)的事件(點擊button 或點擊屏幕)
- Source1:基于Port的 通過內(nèi)核和其他線程相互發(fā)送消息(與內(nèi)核相關(guān))
3. CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變
我們直接來看代碼,給RunLoop添加監(jiān)聽者,監(jiān)聽其運行狀態(tài)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//創(chuàng)建監(jiān)聽者
/*
第一個參數(shù) CFAllocatorRef allocator:分配存儲空間 CFAllocatorGetDefault()默認(rèn)分配
第二個參數(shù) CFOptionFlags activities:要監(jiān)聽的狀態(tài) kCFRunLoopAllActivities 監(jiān)聽所有狀態(tài)
第三個參數(shù) Boolean repeats:YES:持續(xù)監(jiān)聽 NO:不持續(xù)
第四個參數(shù) CFIndex order:優(yōu)先級,一般填0即可
第五個參數(shù) :回調(diào) 兩個參數(shù)observer:監(jiān)聽者 activity:監(jiān)聽的事件
*/
/*
所有事件
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進(jìn)入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽者
/*
第一個參數(shù) CFRunLoopRef rl:要監(jiān)聽哪個RunLoop,這里監(jiān)聽的是主線程的RunLoop
第二個參數(shù) CFRunLoopObserverRef observer 監(jiān)聽者
第三個參數(shù) CFStringRef mode 要監(jiān)聽RunLoop在哪種運行模式下的狀態(tài)
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的內(nèi)存管理(Core Foundation)
凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來的對象,都需要在最后做一次release
GCD本來在iOS6.0之前也是需要我們釋放的,6.0之后GCD已經(jīng)納入到了ARC中,所以我們不需要管了
*/
CFRelease(observer);
}
八. RunLoop處理邏輯


源碼解析
// 共外部調(diào)用的公開的CFRunLoopRun方法,其內(nèi)部會調(diào)用CFRunLoopRunSpecific
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 經(jīng)過精簡的 CFRunLoopRunSpecific 函數(shù)代碼,其內(nèi)部會調(diào)用__CFRunLoopRun函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知Observers : 進(jìn)入Loop
// __CFRunLoopDoObservers內(nèi)部會調(diào)用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函數(shù)
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 核心的Loop邏輯
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers : 退出Loop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
// 精簡后的 __CFRunLoopRun函數(shù),保留了主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
// 通知Observers:即將處理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即將處理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 處理Sources0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
// 處理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
// 如果有Sources1,就跳轉(zhuǎn)到handle_msg標(biāo)記處
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
// 通知Observers:即將休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 進(jìn)入休眠,等待其他消息喚醒
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
do {
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
// 醒來
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopUnsetSleeping(rl);
// 通知Observers:已經(jīng)喚醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg: // 看看是誰喚醒了RunLoop,進(jìn)行相應(yīng)的處理
if (被Timer喚醒的) {
// 處理Timer
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if (被GCD喚醒的) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else { // 被Sources1喚醒的
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
// 執(zhí)行Blocks
__CFRunLoopDoBlocks(rl, rlm);
// 根據(jù)之前的執(zhí)行結(jié)果,來決定怎么做,為retVal賦相應(yīng)的值
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);
return retVal;
}

runloop 休眠實現(xiàn)原理

runloop 休眠時,不占用 CPU,當(dāng)有消息時,CPU 從內(nèi)核態(tài)發(fā)送消息,喚醒線程。
九. RunLoop退出
主線程銷毀RunLoop退出
Mode中有一些Timer 、Source、 Observer,這些保證Mode不為空時保證RunLoop沒有空轉(zhuǎn)并且是在運行的,當(dāng)Mode中為空的時候,RunLoop會立刻退出
我們在啟動RunLoop的時候可以設(shè)置什么時候停止
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
十. RunLoop API
1. runloop 啟動
NSRunLoop 有三種啟動方式:
- Unconditionally(run)
- With a set time limit(runUntilDate)
- In a particular mode(runMode:beforeDate:)
runUntilDate:
- 這個方法,會循環(huán)調(diào)用 runMode:beforeDate: 直到達(dá)到參數(shù) NSDate 所指定的時間,也就是超時時間。
run
- 這個方法,可以看做是 [runloop runUntilDate:[NSDate distantFuture]];
- 相當(dāng)于 while 死循環(huán),一直調(diào)用 runMode:beforeDate:
runMode:beforeDate:
- 這個方法是啟動一次 RunLoop,與前面兩個 API 大不相同,一次 loop 過后就退出 runloop
demo
- (void)setup{
NSLog(@"%@",[[NSRunLoop currentRunLoop] currentMode]);
//創(chuàng)建并啟動一個 thread
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadTest) object:nil];
[self.thread setName:@"Test Thread"];
[self.thread start];
//向 RunLoop 發(fā)送消息的簡便方法,系統(tǒng)會將消息傳遞到指定的 SEL 里面
[self performSelector:@selector(receiveMsg) onThread:self.thread withObject:nil waitUntilDone:NO];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//直接去的子線程 RunLoop 的一個 port,并向其發(fā)送消息,這個是比較底層的 NSPort 方式進(jìn)行線程通信
[self.port sendBeforeDate:[NSDate date] components:[@[[@"hello" dataUsingEncoding:NSUTF8StringEncoding]] mutableCopy] from:nil reserved:0];
});
}
- (void)threadTest{
NSLog(@"%@",@"child thread start");
//threadTest 這個方法是在 Test Thread 這個線程里面運行的。
NSLog(@"%@",[NSThread currentThread]);
//獲取這個線程的 RunLoop 并讓他運行起來
NSRunLoop* runloop = [NSRunLoop currentRunLoop];
self.port = [[NSMachPort alloc]init];
self.port.delegate = self;
[runloop addPort:self.port forMode:NSRunLoopCommonModes];
//約等于runUntilDate:[NSDate distantFuture]
[runloop run];
}
- (void)receiveMsg{
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%@",@"receive msg");
}
#pragma NSMachPortDelegate
- (void)handleMachMessage:(void *)msg{
NSLog(@"handle message thread:%@",[NSThread currentThread]);
}
2.runloop 退出
- 使用 run 啟動 RunLoop,直到程序結(jié)束再退出。主線程的 RunLoop 一定是這種方式啟動的。
- 使用 runUntileDate 啟動 RunLoop,date 時間到退出。這里的 date 會當(dāng)做參數(shù)傳到下面這個方法中,當(dāng) date 時間到,RunLoop 也會因為超時而結(jié)束一次循環(huán)。
- 使用 runMode:beforeDate: 這種方式啟動一次消息循環(huán),并且自己編寫代碼來控制一次消息循環(huán)結(jié)束后是否啟動下一次消息循環(huán)。也就是類似這樣的代碼:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
3.線程?;罘庋b
使用 run 方法開啟 runloop 會使 runloop 一直存活,直到應(yīng)用的生命周期結(jié)束,這樣的線程無法銷毀。如果希望不使用時銷毀線程,需要調(diào)用 runMode:beforeDate 這個 API 結(jié)合 while 去實現(xiàn)?;蛘呤褂?C 語言的 API
方式一:
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MJThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
@end
方式二:
基于 CFRunLoop 的 API 不需要使用 while 循環(huán)實現(xiàn)永久 loop
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.innerThread = [[MJThread alloc] initWithBlock:^{
NSLog(@"begin----");
// 創(chuàng)建上下文(要初始化一下結(jié)構(gòu)體)
CFRunLoopSourceContext context = {0};
// 創(chuàng)建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 銷毀source
CFRelease(source);
// 啟動,第三個參數(shù) returnAfterSourceHandled 為 false 表示永久 loop,為 true 表示單次 loop
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
NSLog(@"end----");
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
十一. RunLoop應(yīng)用
1. 常駐線程
子線程執(zhí)行完操作之后就會立即釋放,即使我們使用強(qiáng)引用引用子線程使子線程不被釋放,也不能給子線程再次添加操作,或者再次開啟。我們可以通過在線程中添加 runloop 的方式讓線程?;?/p>
demo
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic,strong)NSThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 創(chuàng)建子線程并開啟
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
self.thread = thread;
[thread start];
}
-(void)show
{
// 注意:打印方法一定要在RunLoop創(chuàng)建開始運行之前,如果在RunLoop跑起來之后打印,RunLoop先運行起來,已經(jīng)在跑圈了就出不來了,進(jìn)入死循環(huán)也就無法執(zhí)行后面的操作了。
// 但是此時點擊Button還是有操作的,因為Button是在RunLoop跑起來之后加入到子線程的,當(dāng)Button加入到子線程RunLoop就會跑起來
NSLog(@"%s",__func__);
// 1.創(chuàng)建子線程相關(guān)的RunLoop,在子線程中創(chuàng)建即可,并且RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入
// 添加Source [NSMachPort port] 添加一個端口
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 添加一個Timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//創(chuàng)建監(jiān)聽者
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進(jìn)入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監(jiān)聽者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 2.子線程需要開啟RunLoop
[[NSRunLoop currentRunLoop]run];
CFRelease(observer);
}
- (IBAction)btnClick:(id)sender {
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
-(void)test
{
NSLog(@"%@",[NSThread currentThread]);
}
@end
注意:創(chuàng)建子線程相關(guān)的RunLoop,在子線程中創(chuàng)建即可,并且RunLoop中要至少有一個Timer 或 一個Source 保證RunLoop不會因為空轉(zhuǎn)而退出,因此在創(chuàng)建的時候直接加入,如果沒有加入Timer或者Source,或者只加入一個監(jiān)聽者,運行程序會崩潰
2. 自動釋放池
RunLoop內(nèi)部有一個自動釋放池,當(dāng)RunLoop開啟時,就會自動創(chuàng)建一個自動釋放池,當(dāng)RunLoop在休息之前會釋放掉自動釋放池的東西,然后重新創(chuàng)建一個新的空的自動釋放池,當(dāng)RunLoop被喚醒重新開始跑圈時,Timer,Source等新的事件就會放到新的自動釋放池中,當(dāng)RunLoop退出的時候也會被釋放。
注意:只有主線程的RunLoop會默認(rèn)啟動。也就意味著會自動創(chuàng)建自動釋放池,子線程需要在線程調(diào)度方法中手動添加自動釋放池。
@autorelease{
// 執(zhí)行代碼
}
3.解決NSTimer在滑動時停止工作的問題
4.監(jiān)控應(yīng)用卡頓
5.性能優(yōu)化
十二.面試題
1.runloop 和 GCD 的關(guān)系
1.RunLoop 的超時時間
RunLoop 中大量使用到了GCD,啟動 RunLoop 的超時時間就是使用 GCD 中的 dispatch_source_t來實現(xiàn)的
2.runloop 去執(zhí)行 GCD MainQueue 上的異步任務(wù)
dispatch_async(dispatch_get_main_queue(), block)產(chǎn)生的任務(wù)需要通過 runloop 去調(diào)度
所以 runloop 和 GCD 是相互調(diào)用依賴的關(guān)系
2.runloop 與 autorelease pool 的關(guān)系
第一個 Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是-2147483647,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入睡眠) 和 Exit(即將退出Loop),
BeforeWaiting(準(zhǔn)備進(jìn)入睡眠)時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;
Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。