美文网首页selector
RunLoop 和线程

RunLoop 和线程

作者: Stago | 来源:发表于2017-02-17 16:22 被阅读16次

    RunLoop 和 线程的关系 (基本理解)

    RunLoop 是线程的基础架构部分,Cocoa和CoreFundation都提供了RunLoop对象方便配置和管理线程的RunLoop。每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
    RunLoop与线程是一一对应的,一个RunLoop对应一个核心的线程,为什么说是核心的,是因为RunLoop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。
    RunLoop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
    RunLoop在第一次获取时被创建,在线程结束时被销毁。
    对于主线程来说,RunLoop在程序一启动就默认创建好了。
    对于子线程来说,RunLoop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的RunLoop被创建,不然定时器不会回调。
    但是需要注意:虽然每一个线程都可以获取RunLoop对象,但是并不是每一个线程中都有实例对象,我们可以这样理解:如果我们不获取RunLoop,这个RunLoop就不存在,我们获取时,如果不存在,就会去创建。在主线程中,这个MainRunLoop是默认创建并运行激活的。

    iOS的应用程序启动后会有一个如下的main()函数

    int main(int argc, char * argv[]) {   
       @autoreleasepool {        
        return UIApplicationMain(argc, argv, nil,  
                 NSStringFromClass([AppDelegate class]));   
       }
    }
    

    这个方法会为main thread设置一个NSRunLoop对象,为什么是我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
    对其它线程来说,NSRunLoop 默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
    在任何一个Cocoa程序的线程中,都可以通过以下代码来获取到当前线程的NSRunLoop。

    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    

    RunLoop执行完毕之后,就会进入休眠 , 只有在某个情况下触发了,才会再次调用;

    RunLoop 和 线程的关系 (代码分析)

    首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。

    苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样:

    /// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
    static CFMutableDictionaryRef loopsDic;
    /// 访问 loopsDic 时的锁
    static CFSpinLock_t loopsLock;
     
    /// 获取一个 pthread 对应的 RunLoop。
    CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
        OSSpinLockLock(&loopsLock);
        
        if (!loopsDic) {
            // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
            loopsDic = CFDictionaryCreateMutable();
            CFRunLoopRef mainLoop = _CFRunLoopCreate();
            CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
        }
        
        /// 直接从 Dictionary 里获取。
        CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
        
        if (!loop) {
            /// 取不到时,创建一个
            loop = _CFRunLoopCreate();
            CFDictionarySetValue(loopsDic, thread, loop);
            /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
            _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
        }
        
        OSSpinLockUnLock(&loopsLock);
        return loop;
    }
     
    CFRunLoopRef CFRunLoopGetMain() {
        return _CFRunLoopGet(pthread_main_thread_np());
    }
     
    CFRunLoopRef CFRunLoopGetCurrent() {
        return _CFRunLoopGet(pthread_self());
    }
    

    从上面的代码也可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

    相关文章

      网友评论

        本文标题: RunLoop 和线程

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