RunLoop是iOS中處理循環(huán)事件以及管理和處理消息的對象冒晰,通過在runloop中注冊不同的觀察者對象和回調(diào)處理來處理source0和source1事件览绿。通過簡單的的示例我們來觀察一下為什么子線程RunLoop需要手動開啟轨域。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"2");
});
}
-(void)test{
NSLog(@"3");
}
我們運(yùn)行一下可看到如下打印效果 :
1 2
如果我們在子線程中獲取該線程中的runloop祷舀,我們在看下打印結(jié)果
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
[[NSRunLoop currentRunLoop] run];
NSLog(@"2");
});
}
-(void)test{
NSLog(@"3");
}
輸出結(jié)果如下:
1 3 2
如同我們所知道的那樣鉴未,performSelector:withObject:afterDelay:
是在當(dāng)前runloop中注冊了一個Timer事件舶吗,但是當(dāng)前線程并沒有處理該Timer事件,結(jié)合runloop源碼我們探索一下
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
_CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
_CFUnlock(&loopsLock);
// __CFRunLoops是個CFMutableDictionaryRef類型的全局變量洒疚,用來保存RunLoop歹颓。這里首先判斷有沒有這個全局變量,如果沒有就新創(chuàng)建一個這個全局變量油湖,并同時創(chuàng)建一個主線程對應(yīng)的runloop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 創(chuàng)建一個主線程對應(yīng)的runloop巍扛。
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);
}
// 根據(jù)線程取其對應(yīng)的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果這個線程沒有對應(yīng)的runloop,就新建立一個runloop對象
if (!loop) {
//RunLoop 懶加載的方式7Φ隆3芳椤!
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
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)) {
// 注冊一個回調(diào)喊括,當(dāng)線程銷毀時一同銷毀對應(yīng)的runloop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
當(dāng)調(diào)用 [NSRunloop currentLoop]
的時候胧瓜,會將當(dāng)前線程的指針傳入_CFRunLoopGet0
函數(shù)中,會將當(dāng)前線程和當(dāng)前線程對應(yīng)的runloop對象保存在一個全局的容器里面郑什,如果當(dāng)前線程沒有runloop府喳,會使用懶加載的方式創(chuàng)建一個runloop,并保存到全局的容器中蘑拯,這也就解釋了為什么子線程的runloop需要手動的獲取钝满,而不是默認(rèn)開啟的了肉津,并且每個子線程和該線程下的runloop是一一對應(yīng)的關(guān)系。
附件 Runloop 文件 https://github.com/DevaLee/Runloop.git