美文网首页
RunLoop(1)--runloop创建运行的底层逻辑

RunLoop(1)--runloop创建运行的底层逻辑

作者: Mr大喵喵 | 来源:发表于2020-11-11 18:24 被阅读0次

    什么是RunLoop

    顾名思义:运行循环,在程序的运行过程中去循环的做些事情。
    RunLoop 实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件。比如:

    1. 定时器(Timer)、PerformSelector(到指定的线程/model/延长执行的这些都与runloop相关)
    2. GCD Async Main Queue(一般是从GCD的子线程回到主线程会调用到这个方法)
    3. 事件响应、手势识别、界面刷新(source0/source1的这些事件)
    4. 网络请求(子线程的保活)
    5. AutoreleasePool
    

    以上的这些场景基本都与runloop息息相关。RunLoop在有事的时候去运作,在没有事件处理的时候,会使线程进入睡眠模式,从而节省 CPU 资源,提高程序性能。

    那在我们的程序中runloop是如何表现的呢?

    我们先来看看对于我们的程序,runloop的作用。

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            NSLog(@"hello world!");
        }
        return 0;
    }
    如果没有runloop,执行完nslog打印代码后,会即将退出程序。
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    这是有runloop的时候我们的main函数,其实内部逻辑相当于:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int result = 0;
            do {
                    // 睡眠中去等待消息
                  int message = sleep_and_wait();
                  // 获取消息,看是否有消息需要处理
                 result = process_message(message);
            } while(result == 0);
        }
        return 0;
    }
    

    runloop的内部逻辑,就是一个do-while循环,让程序不会马上退出,而是保持运行状态。当有事件处理的时候就去处理,没有事件的时候就去休眠,节省cpu的资源。

    runloop对象

    iOS中我们无法主动去创建runloop,我们只能去通过API去获取runloop。iOS中目前有2套API来让我们访问和使用RunLoop

    Foundation:NSRunLoop
    
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    
    Core Foundation:CFRunLoopRef
    
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    

    NSRunLoopCFRunLoopRef都代表着RunLoop对象
    NSRunLoop是基于CFRunLoopRef的一层OC包装
    CFRunLoopRef是开源的

    runloop.jpeg
    我们主要看CFRunLoopRef的源码,在runloop中相关5个比较重要的对象类:
    1. CFRunLoopRef
    2. CFRunLoopModeRef
    3. CFRunLoopSourceRef
    4. CFRunLoopTimerRef
    5. CFRunLoopObserverRef
    

    我们主要看两个类分析:__CFRunLoop__CFRunLoopMode

    typedef struct __CFRunLoop * CFRunLoopRef;
    struct __CFRunLoop {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;          /* locked for accessing mode list */
        __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
        Boolean _unused;
        volatile _per_run_data *_perRunData;              // reset for runs of the run loop
        pthread_t _pthread;
        uint32_t _winthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFTypeRef _counterpart;
    };
    struct __CFRunLoopMode {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
        CFStringRef _name;
        Boolean _stopped;
        char _padding[3];
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        CFMutableDictionaryRef _portToV1SourceMap;
        __CFPortSet _portSet;
        CFIndex _observerMask;
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
        dispatch_source_t _timerSource;
        dispatch_queue_t _queue;
        Boolean _timerFired; // set to true by the source when a timer has fired
        Boolean _dispatchTimerArmed;
    #endif
    #if USE_MK_TIMER_TOO
        mach_port_t _timerPort;
        Boolean _mkTimerArmed;
    #endif
    #if DEPLOYMENT_TARGET_WINDOWS
        DWORD _msgQMask;
        void (*_msgPump)(void);
    #endif
        uint64_t _timerSoftDeadline; /* TSR */
        uint64_t _timerHardDeadline; /* TSR */
    };
    

    我们简化一些两个类的内容:__CFRunLoop__CFRunLoopMode

    typedef struct __CFRunLoop * CFRunLoopRef;
    struct __CFRunLoop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
    };
    struct __CFRunLoopMode {
        CFStringRef _name;
      
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    };
    

    CFRunLoopModeRef代表RunLoop的运行模式
    从上面的代码中也能看出: 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

    RunLoop相关类关系图.png

    RunLoop的获取逻辑

    上面我们也说到了在oc中获取runloop的两种方式,那他们的底层是如何操作的呢?

    CFRunLoopRef CFRunLoopGetMain(void) {
        CHECK_FOR_FORK();
        static CFRunLoopRef __main = NULL; // no retain needed
        if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
        return __main;
    }
    
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        __CFSpinLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFSpinUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        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);
            __CFSpinLock(&loopsLock);
        }
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFSpinUnlock(&loopsLock);
        if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFSpinLock(&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
            __CFSpinUnlock(&loopsLock);
        CFRelease(newLoop);
        }
        if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    

    从上面的源码中我们可以分析出:
    1 . 每条线程都有唯一的一个与之对应的RunLoop对象
    2 . RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    3 . 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    4 . RunLoop会在线程结束时销毁
    5 . 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

    RunLoop对象的运行run

    上面我们看到了RunLoop对象内部如何创建的过程,然后runloop要怎么run起来呢?

    void CFRunLoopRun(void) {   /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    

    看了源码我们也能看到和我们开篇说的那样:
    RunLoop其实内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer),通过判断result的值实现的。所以 可以看成是一个死循环。如果没有RunLoopUIApplicationMain 函数执行完毕之后将直接返回,就是说程序一启动然后就结束。
    同时我们也能看到:
    kCFRunLoopDefaultMode,默认情况下,runLoop是在这个mode下运行的,

    相关文章

      网友评论

          本文标题:RunLoop(1)--runloop创建运行的底层逻辑

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