美文网首页iOS 面试专项
为什么子线程RunLoop需要手动开启

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

作者: Bel李玉 | 来源:发表于2020-01-06 22:46 被阅读0次

    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

    相关文章

      网友评论

        本文标题:为什么子线程RunLoop需要手动开启

        本文链接:https://www.haomeiwen.com/subject/nxslactx.html