Runloop底层原理

308 阅读5分钟

Runloop 介绍

从字面的意思来看Runloop是运行循环,而它也是一个对象,可以提供一个入口函数,程序就会进入一个do...while循环,保证我们当前应用不被退出。

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其实就是一种消息机制处理模式,比如,我点击一下屏幕、调用一个Timer、或执行performSeletor一个方法,这些都属于消息机制,但它都会交给Runloop进行处理。

Runloop 的作用

  1. 保持程序的持续运行
  2. 处理APP中的各种事件(触摸、定时器、performSeletor)
  3. 节省CPU资源、提高程序的性能:有活就干,没活就休息

Runloop 的item:

  • block应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK

  • 调用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

  • 响应source0:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

  • 响应source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION

  • GCD主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE

  • observer源:CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

以上这些源都会调用我们Runloop的底层。

Runloop和线程的关系

线程和Runloop通过一个可变的全局CFMutableDictionaryRef进行Key-Value的存储,所以他们之间的关系是一一对应。

Runloop的创建

CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

Runloop的存储

CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

Runloop的获取

CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

注意:

  • 子线程的Runloop默认不开启
  • 事件源依赖于Runloop

Runloop 源码分析

Runloop是一个对象结构体,是利用线程创建的,默认的mode是kCFRunLoopDefaultMode。

  • CFRunLoopRef
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;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
  • CFRunLoopModeRef

mode的类型:

  1. kCFRunLoopDefaultMode
  2. UITrackingRunLoopMode
  3. UIInitializationRunLoopMode
  4. GSEventReceiveRunLoopMode
  5. kCFRunLoopCommonModes
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 */
};

那我们总结一下Runloop的结构,如下图:

首先,Runloop对象与线程是一一对应关系,并且Runloop是依赖于线程,所以操作线程就可以控制Runloop。 其次,RunLoop定义的结构体中包括一个CFRunloopMode的人集合,所以,他们之间是一对的关系。 最后,CFRunLoopMode定义的结构体中包括多个源的集合,所以,mode与各种源也是一对多的关系。

注意:程序中一次运行只能依赖一个mode

一个Mode包含的items:

  1. CFRunLoopAddTimer
  2. CFRunLoopAddObserver
  3. CFRunLoopAddSource
  4. CFSetAddValue(rl->_commonModeItems, rlt) 储存Item到对应的mode里面
  5. __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) 调用我们之前存储的Item
  • doIt = CFEqual
  • doIt = CFSetContainsValue
  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK(block)
  • block()
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    if (!rl->_blocks_head) return false;
    if (!rlm || !rlm->_name) return false;
    Boolean did = false;
    struct _block_item *head = rl->_blocks_head;
    struct _block_item *tail = rl->_blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
        Boolean doit = false;
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            // timer 加入的mode 和 我们现在runloop的mode 相等
            // curr->_mode = kCFRunLoopCommonModes 相等
            // 事务就能执行

            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
        if (!doit) prev = curr;
        if (doit) {
            if (prev) prev->_next = item;
            if (curr == head) head = item;
            if (curr == tail) tail = prev;
            void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
            if (doit) {
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
                did = true;
            }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
        }
    }
  • CFRunloopTimer

timer 的底层就是一个CFRunLoopTimerRef,这个timer是受我们的Runloop模式切换的影响

  1. timer 一定要加入相应的Runloop 的mode中,底层就是将timer加到items
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            // timer -- items()
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
}

  1. 当Runloop run的时候,就会执行doblock,进入while循环,遍历item->next,判断doit成立后,对我们当前的item进行一个block调用。
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    if (!rl->_blocks_head) return false;
    if (!rlm || !rlm->_name) return false;
    Boolean did = false;
    struct _block_item *head = rl->_blocks_head;
    struct _block_item *tail = rl->_blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
        Boolean doit = false;
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            // timer 加入的mode 和 我们现在runloop的mode 相等
            // curr->_mode = kCFRunLoopCommonModes 相等
            // 事务就能执行

            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
        if (!doit) prev = curr;
        if (doit) {
            if (prev) prev->_next = item;
            if (curr == head) head = item;
            if (curr == tail) tail = prev;
            void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
            if (doit) {
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
                did = true;
            }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
        }
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    if (head) {
        tail->_next = rl->_blocks_head;
        rl->_blocks_head = head;
        if (!rl->_blocks_tail) rl->_blocks_tail = tail;
    }
    return did;
}
  • CFRunloopObserver

监听当前Runloop的状态,只要发生变化,就会触发observer的回调,如果没有任务执行,会进入休眠状态

  • source0

包含回调函数指针

  1. signal 待处理
  2. wakeup 唤醒Runloop 处理事件

主要用于app内部事件,自己负责管理 的事务(UIEvent、CFSocket)

调用底层:

  1. 因为source0只包含函数指针,它并不能主动触发事件
  2. CFRunLoopSourceSignal(source)将这个事件标记为待处理
  3. CFRunLoopWakeUp来唤醒Runloop,让他处理事件

CFRunLoopSOurceContext

创建Source 0 :

CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);

添加到Runloop:

CFRunLoopRef rlp = CFRunLoopGetCurrent();
    // source --> runloop 指定了mode  那么此时我们source就进入待绪状态
    CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);

执行信号:

 // 一个执行信号
    CFRunLoopSourceSignal(source0);

唤醒Runloop去处理:

// 唤醒 run loop 防止沉睡状态
    CFRunLoopWakeUp(rlp);

取消移除源:

 // 取消 移除
    CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);

CFRlease(rlp)

  • source1
  1. 包含一个mach_port & 回调函数指针

  2. 被用于通过内核和其他线程相互发送消息

Runloop 原理

任何的事件源处理都是通过Observers 监听通知Runloop运行的。

如下图: