iOS 面试专项

为什么子线程RunLoop需要手动开启

2020-01-06  本文已影响0人  Bel李玉

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

上一篇下一篇

猜你喜欢

热点阅读