RunLoop是iOS中处理循环事件以及管理和处理消息的对象,通过在runloop中注册不同的观察者对象和回调处理来处理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");
}
我们运行一下可看到如下打印效果 :
1 2
如果我们在子线程中获取该线程中的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];
[[NSRunLoop currentRunLoop] run];
NSLog(@"2");
});
}
-(void)test{
NSLog(@"3");
}
输出结果如下:
1 3 2
如同我们所知道的那样,performSelector:withObject:afterDelay:
是在当前runloop中注册了一个Timer事件,但是当前线程并没有处理该Timer事件,结合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。这里首先判断有没有这个全局变量,如果没有就新创建一个这个全局变量,并同时创建一个主线程对应的runloop
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建一个主线程对应的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);
}
// 根据线程取其对应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果这个线程没有对应的runloop,就新建立一个runloop对象
if (!loop) {
//RunLoop 懒加载的方式!!!
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)) {
// 注册一个回调,当线程销毁时一同销毁对应的runloop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
当调用 [NSRunloop currentLoop]
的时候,会将当前线程的指针传入_CFRunLoopGet0
函数中,会将当前线程和当前线程对应的runloop对象保存在一个全局的容器里面,如果当前线程没有runloop,会使用懒加载的方式创建一个runloop,并保存到全局的容器中,这也就解释了为什么子线程的runloop需要手动的获取,而不是默认开启的了,并且每个子线程和该线程下的runloop是一一对应的关系。
附件 Runloop 文件 https://github.com/DevaLee/Runloop.git
网友评论