沒有runloop 就意味著app一運行就會退出(換句話說,runloop保護著app不會被退出)
- NSRunLoop - Foundation 框架
- CFRunLoop - CoreFoundation 框架下 (NSRunloop就是CFRunloop的封裝)
下載地址: https://opensource.apple.com/tarballs/CF/CF-1151.16.tar.gz
RunLoop和線程的關(guān)系
- 和線程是1對1的(存在于一個全局的map中, 線程作為key, runLoop 作為value)
- 線程中默認是沒有runloop的(主線程在AppDelegate中會自動獲取(創(chuàng)建) )
- 通過
[NSRunLoop currentRunLoop];創(chuàng)建runLoop; - 通過
[NSRunLoop mainRunLoop];獲取主線程runLoop; - Runloop會在線程結(jié)束的時候銷毀
RunLoop創(chuàng)建源碼分析
- 注意閱讀注釋
[CFRunLoop currentRunLoop];
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// t : 傳入的當(dāng)前線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// __CFRunLoops 靜態(tài)map 初始化為null
if (!__CFRunLoops) {
// 創(chuàng)建全局 runloopMap和主線程的runloop,
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 將 dict 賦值給__CFRunLoops (搞不懂__CFRunLoops的可以看這里)
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 使用 入?yún)⒌?t 在 全局的map中 獲取到響應(yīng)的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果沒有獲取到runloop, 則新建一個runloop
if (!loop) {
// 新建一個
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
// 再次獲取當(dāng)先線程runloop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 確定當(dāng)前線程沒有runloop 將上面新創(chuàng)建的newloop保存到全局的map中
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;
}
可以自己創(chuàng)建監(jiān)聽來監(jiān)聽Runloop的狀態(tài)
// 通過block的方式回調(diào)
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"######進入kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"######即將處理Timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"######即將處理Source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"######即將休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"######被喚醒");
break;
case kCFRunLoopExit:
NSLog(@"######退出RunLoop");
break;
default:
break;
}
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"當(dāng)前runloop模式 - %@", mode);
CFRelease(mode);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
RunLoop的應(yīng)用
定時器失效
Q: 定時器失效問題
A: 因為timer模式是工作在runloop的kCFRunLoopDefaultMode模式下,
當(dāng)滑動scorllView時候runloop會切換到UITrackingRunLoopMode模式下,所以導(dǎo)致timer定時器失效;
解決方法
// 可以將timer標記為CommonModes
[NSRunLoop currentRunLoop]addTimer:timer forMode:kCFRunLoopCommonModes];
線程?;?/p>
需要保護線程不退出可以給子線程添加runloop來保證線程不被退出
如果需要在適當(dāng)?shù)臅r候銷毀線程 runloop 一定不能通過 [runloop run] 方法開啟
PS: runloop提供的run方法是無法銷毀的,其專門用于開啟一個永不銷毀的線程
如果需要在適當(dāng)?shù)臅r候銷毀子線程, 就需要自己實現(xiàn)runloop的開啟方法
// 這一句 是在dealloc中執(zhí)行stop函數(shù)時防止對象已經(jīng)被釋放導(dǎo)致weakSelf為nil, 判斷失效導(dǎo)致runloop無法銷毀
// while(weakSelf && !wealSelf.isStop)
while(!isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// 當(dāng)需要結(jié)束線程的時候 在相關(guān)的方法中 將 stop 標記為no 用以結(jié)束runloop 從而退出線程
-(void)stop{
// 注意! waitUntilDone 參數(shù)需要設(shè)置為YES, 不然如果在dealloc中執(zhí)行stop函數(shù) waitUntilDone設(shè)置為NO的話會進行異步操作導(dǎo)致野指針
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)stopThread{
isStop = false;
CFRunLoopStop(CFRunLoopGetCurrent())
}