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

什么是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创建运行的底层逻辑)