iOS底层原理08: RunLoop

896 阅读4分钟

一、RunLoop是什么

1. RunLoop概念

RunLoop是在程序运行中处理时间的循环, 每个线程都有一个Runloop与之对应, 当Runloop有事件需要处理时, 就会进行事件处理; 没有事件时, 线程就会进入休眠.

在iOS中, 有NSRunLoop和CFRunLoopRef两种, 而前者是后者的封装.

2. RunLoop的作用

RunLoop主要可以处理定时器, GCD, 事件响应, UI刷新, 网络请求以及AutoReleasePool等

3. RunLoop相关源码定义

Core Foundation中, RunLoop相关的有5个主要的类

  • CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {
    pthread_mutex_t _lock;	// 处理modes用到的锁
    __CFPort _wakeUpPort;	// 唤醒RunLoop的Port
    pthread_t _pthread;    // RunLoop对应的线程
    CFMutableSetRef _commonModes; // commonModes模式下的modes集合
    CFMutableSetRef _commonModeItems;   // CommondModeItem 的集合
    CFRunLoopModeRef _currentMode; // 当前运行的mode
    CFMutableSetRef _modes;  // 所有的modes

    // ...
};
  • CFRunLoopModeRef
  1. RunLoop在运行中只能运行在一个Mode中, 如果需要切换mode, 只能将当前mode停止, 然后用新mode重新启动;
  2. mode中存储的有当前的sources0, sources1, observer和timer等;
  3. 如果当前RunLoop中没有sources0等, 那么RunLoop就会立即退出
  4. Mode常见的有2中: kCFRunLoopDefaultMode(App的默认Mode, RunLoop通常会运行在这个Mode), UITrackingRunLoopMode(用于界面跟踪, 当ScrollView滑动时保证滑动不受其他Mode的影响)
typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    CFMutableSetRef _sources0; 
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
  • CFRunLoopSourceRef
  1. Source0: 主要包含触摸事件, performSelector事件等
  2. Source1: 主要包含基于Port的线程通信, 系统事件捕捉
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;	/* Source0 */
        CFRunLoopSourceContext1 version1;	/* Source1 */
    } _context;
};

  • CFRunLoopTimerRef
  1. NSTimer
  2. performSelector延迟执行事件
typedef struct  __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;  // 启动时间
    CFTimeInterval _interval;	// 时间间隔
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
  • CFRunLoopObserverRef
  1. 监听RunLoop状态的通知
  2. UI刷新的通知 (BeforeWaiting)
  3. Autorelease Pool 的通知 (BeforeWaiting)
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;	// Observer状态
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   // 进入
    kCFRunLoopBeforeTimers = (1UL << 1),    // 处理timer前
    kCFRunLoopBeforeSources = (1UL << 2),   // 处理source前
    kCFRunLoopBeforeWaiting = (1UL << 5),   // 休眠前
    kCFRunLoopAfterWaiting = (1UL << 6),    // 休眠后
    kCFRunLoopExit = (1UL << 7),    // 退出前
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

4. 获取当前RunLoop

  • CFRunLoopGetCurrent 通过CFRunLoopGetCurrent可以获取当前的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
  • _CFRunLoopGet0 如果直接找不到, 会调用这个方法查找, 并且参数是当前线程. 可以证明: RunLoop是与线程一一绑定的
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        // 如果传入的线程为空, 则默认为主线程
        t = pthread_main_thread_np();
    }
    
    __CFLock(&loopsLock);
    // 如果是第一次进入, 则需要创建一个全局的字典
    if (!__CFRunLoops) { 
        __CFUnlock(&loopsLock);
        // 创建字典
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);

        // 为主线程创建RunLoop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

        // 将主线程与RunLoop加入字典中
        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);
    if (!loop) {

        // 如果全局字典中没有, 则传入当前线程t创建一个新的RunLoop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            // 将RunLoop与线程放入全局字典中
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        __CFUnlock(&loopsLock);
        // 将newLoop赋值给loop后释放
        CFRelease(newLoop);
    }

    return loop;
}

二、RunLoop运行过程

文字描述版

  1. 通知Observer: 进入Loop
  2. 通知Observer: 即将处理Timer
  3. 通知Observer: 即将处理Source
  4. 处理Blocks
  5. 处理Source0, 可能再次处理Blocks
  6. 如果存在Source1, 就跳转到步骤8
  7. 通知Observer: 开始休眠, 等待被唤醒
  8. 通知Observer: 结束休眠, 处理响应
    1. 处理Timer
    2. 处理GCD消息
    3. 处理Source1
  9. 处理Blocks
  10. 根据前边的执行结果决定
    1. 回到步骤2继续Loop
    2. 退出Loop
  11. 通知Observer: 退出Loop

图片版本(图片来自网络)

runloop.jpeg

源码验证

  1. CFRunLoopRun CFRunLoopRun中其实就是开启一个do-while循环, 如果result不是Stop或Finish, 就会一直循环下去
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  1. CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    // 如果当前runloop被释放, 直接返回finish
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl); // 加锁runloop

    // 拿到runloop要进入的currentMode(已经加锁)
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    // 当前没有mode, 或mode里边没有source/timer/observer等 就进行解锁并且返回
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }

    // 将runloop的当前mode修改并记录
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;

    // 初始化result
    int32_t result = kCFRunLoopRunFinished;
    
    // 如果当前当前mode是即将进入, 则通知 Observers :Entry (步骤1)
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    // 进行runloop的内部函数
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    // 如果当前当前mode是即将退出, 则通知 Observers :Exit (步骤11)
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    // 解锁mode
    __CFRunLoopModeUnlock(currentMode);
    // 将之前的mode复原
    rl->_currentMode = previousMode;
    // 解锁runloop
    __CFRunLoopUnlock(rl);
    return result;
}
  1. __CFRunLoopRun内部函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    // 提前进行一些判断返回Stop
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    
    // 初始化返回值
    int32_t retVal = 0;
    do {
        // 设置runloop 取消 忽略唤醒 (可以接收消息)
        __CFRunLoopUnsetIgnoreWakeUps(rl);

        // 如果mode是Timer, 通知Observer: 即将处理Timer (步骤2)
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        // 如果mode是Sources, 通知Observer: 即将处理Sources (步骤3)
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // runloop处理Blocks (步骤4)
        __CFRunLoopDoBlocks(rl, rlm);
        
        // runloop处理Source0, 处理以后可能再次处理Blocks (步骤5)
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            // 如果有基于MachPort的Source1, 则直接跳转到 handle_msg进行处理 (步骤6)
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        
        // 通知Observer: 即将开始休眠 (步骤7)
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl); // 进入休眠等待被唤醒
        
        // 休眠时可以解锁runloop和mode
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        // 记录开始休眠的时间, 用于计算休眠了多久
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
        do {
            msg = (mach_msg_header_t *)msg_buffer;
            // 被MachPort唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {

                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) { 
                    // timer超时唤醒
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                break;
            }
        } while (1);

        
        
        // 被唤醒时要对runloop和mode重新加锁
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        
        // 计算runloop的休眠时间
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        
        // 设置runloop 忽略唤醒 (不在接收消息)
        __CFRunLoopSetIgnoreWakeUps(rl);

        // 通知Observer:  runloop被唤醒 (步骤8)
        __CFRunLoopUnsetSleeping(rl);
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    // 处理被唤醒的消息
    handle_msg:;

        // 设置runloop 忽略唤醒 (不在接收消息)
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 处理timer
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        else if (livePort == dispatchPort) {
            // 处理GCD
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            // 处理Source1
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
            
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        }
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        // 根据执行结果设置 retVal
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
                
    } while (0 == retVal);

    return retVal;
}