深入理解Runloop

注:本篇博客和 ibireme深入理解RunLoop 基本一样,稍微根据自己的实际学习进行了一些修改。

学习过程中用的是swift-corelibs-foundation版本的RunLoop进行阅读学习。

能力有限,如果有理解的不对的地方,还希望能帮忙指出。

一般来说,一个线程一次只能执行一个任务,执行完毕之后线程就会退出。例如一个hello world程序,在执行完毕之后就会退出。

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("hello world");
}
//输出
hello world
//线程退出

那我们的App是怎么做到让我们的主线程不退出,且可以随时处理我们的事件呢?这里就我们就需要用到Event Loop。通常它会像这样:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

Event Loop 在很多系统和框架里都有实现,macOS/iOS中使用的是RunLoop

RunLoop实际是一个对象,负责处理接收到的事件和消息,并提供了一个入口函数来执行上面的Event Loop逻辑。线程执行了这个函数之后,这个函数内部就会处于“接收消息→等待→处理”的循环中,直到循环结束(比如传入quit的消息),函数返回。

macOS/iOS系统中提供了两个对象:NSRunLoopCFRunLoopRef

  • CFRunLoopRefCoreFoundation框架中,提供了纯C函数的API,并且所有API都是线程安全的。
  • NSRunLoop则是基于CFRunLoopRef的封装,提供面向对象的API,但是这些API是非线程安全的。

CFRunLoopRef的代码开源可以在https://opensource.apple.com/tarballs/CF/进行下载源码查看。

Swift开源后,苹果又维护了一个跨平台的CoreFoundatio 版本:https://github.com/apple/swift-corelibs-foundation/。直接下载release版本的代码,可以直接编译通过,然后你就可以单步调试玩了。好评~

注意:下面的代码都是swift-corelibs-foundation工程里面的代码。

1. RunLoop与线程的关系

首先,iOS 开发中能遇到两个线程对象:pthread_tNSThread。过去苹果有份文档指明了NSThread只是pthread_t的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的mach thread

虽然,苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是pthread_tNSThread是一一对应的。比如,可以通过pthread_main_thread_np()[NSThread mainThread]来获取主线程;也可以通过pthread_self()[NSThread currentThread]来获取当前线程。此外,CFRunLoop是基于pthread来管理的。

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:

CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);

这两个函数内部的逻辑大概是下面这样:

/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
/// 访问__CFRunLoops时的锁
static CFLock_t loopsLock = CFLockInit;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
    // 如果为空则t设置为主线程
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    // 访问__CFRunLoops字典时加锁
    __CFLock(&loopsLock);
    // 没有__CFRunLoops字典时创建一个
    if (!__CFRunLoops) {
        // 初始化全局的__CFRunLoops字典,并先为主线程创建一个RunLoop
        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);
    }
    // 通过线程取出相应的RunLoop
    CFRunLoopRef newLoop = NULL;
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        // 取不到时创建一个,并存入字典
        newLoop = __CFRunLoopCreate(t);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    if (newLoop) { CFRelease(newLoop); }
    
    // 如果传入线程和当前线程相同
    if (pthread_equal(t, pthread_self())) {
        // 注册一个回调,当线程销毁是,顺便也销毁对应的RunLoop
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
#else
            _CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif
        }
    }
    return loop;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}

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;
}

从上面的代码可以看出:

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

2. RunLoop对外的接口

在 CoreFoundation 里面关于 RunLoop 有5个类:

typedef CFStringRef CFRunLoopMode CF_EXTENSIBLE_STRING_ENUM;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
  • CFRunLoopMode

CFRunLoopModeRef类并没有对外暴露,只是通过CFRunLoopRef接口进行了封装。他们的关系如下:

RunLoop Mode

一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer。每次调用RunLoop的主函数时,只能指定其中一个Mode,这个Mode被称作CurrentMode。如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入。这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响。

  • CFRunLoopSourceRef

    CFRunLoopSourceRef是事件产生的地方。Source有两个版本:Source0Source1

    • Source0只包含了一个回调(函数指针),它并不能主动触发事件

      使用时,需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。

    • Source1包含了一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。

      这种Source主动唤醒RunLoop的线程,其原理在下面会讲到。

  • CFRunLoopTimerRef

    CFRunLoopTimerRef 是基于时间的触发器,它和NSTimertoll-free bridged的,可以混用。其包含一个时间长度和一个回调(函数指针)。

    当其加入到RunLoop时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

  • CFRunLoopObserverRef

    CFRunLoopObserverRef是观察者,每个Observer都包含了一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。

    可以观测的时间点有以下几个:

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),// 即将进入Loop
        kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer
        kCFRunLoopBeforeSources = (1UL << 2),// 即将处理Source
        kCFRunLoopBeforeWaiting = (1UL << 5),// 即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
        kCFRunLoopExit = (1UL << 7),// 即将退出Loop
        kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有事件
    };
    

上面的Source/Timer/Observer被统称为 mode item,一个item可以被同时加入多个mode。但一item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则RunLoop会直接退出,不进入循环。

3. RunLoop的Mode

CFRunLoopModeCFRunLoop的结构大致如下:

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
};

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

这里有个概念叫CommonModes:一个Mode可以将自己标记为Common属性(通过将其ModeName添加到RunLoopcommonModes中)。每当RunLoop的内容发生变化时,RunLoop都会自动将 _commonModeItems里的Source/Observer/Timer同步到具有Common标记的所有Mode里。

应用场景举例:

主线程的RunLoop里有两个预置的Mode

  • kCFRunLoopDefaultMode
  • UITrackingRunLoopMode

这两个Mode都已经被标记为Common属性。DefaultMode是App平时所处的状态,TrackingRunLoopMode是追踪ScrollView滑动时的状态。当你创建一个Timer并加到DefaultMode时,Timer会得到重复回调,但此时滑动一个TableView时,RunLoop会将mode切换为TrackingRunLoopMode,这时Timer就不会被回调,并且也不会影响到滑动操作。

有时你需要一个Timer,在两个Mode中都能得到回调,一种办法就是将这个Timer分别加入这两个Mode。还有一种方式,就是将Timer加入到顶层的RunLoopcommonModeItems中。commonModeItemsRunLoop自动更新到所有具有Common属性的Mode里去。

CFRunLoop对外暴露的管理Mode接口只有下面2个:

CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);
CF_EXPORT CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

Mode暴露的管理mode item的接口有下面几个:

CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);

可以注意到mode并不像Source/Timer/Observer一样有 Remove 方法,所以mode只能增加,不能减少。

当你传入一个新的mode nameRunLoop内部没有对应mode时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef

苹果公开提供的Mode有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个Mode Name来操作其对应的Mode

同时苹果还提供了一个操作Common标记的字符串:kCFRunLoopCommonModes(NSRunLoopCommonModes),你可以用这个字符串来操作Common Items,或标记一个ModeCommon。使用时注意区分这个字符串和其他mode name

4. RunLoop的内部逻辑

根据苹果在文档里的说明,RunLoop 内部的逻辑大致如下:

RunLoop流程

RunLoop的启动:

/// 用DefaultMode启动
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
/// 用指定的Mode启动,并允许设置RunLoop的超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

我们可以看到最后都是调用了CFRunLoopRunSpecific

/// RunLoop的实现
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    // 安全监测,kCFRunLoopCommonModes可以理解为一个占位符,并不是一个特定的Mode
    // 在启动时NULL和kCFRunLoopCommonModes是不能使用的
    if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
            _CFRunLoopError_RunCalledWithInvalidMode();
        });
        return kCFRunLoopRunFinished;
    }
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 根据modeName找到对应的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 如果没有找到,或者source/timer/observer为空,则直接返回
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    // 一些初始化配置
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    
    // 1.通知Observers:RunLoop即将进入loop
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_START, rl, currentMode, seconds, previousMode);
    // 内部函数,进入loop循环
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_END, rl, currentMode, seconds, previousMode);
    
    // 10.通知Observers:RunLoop即将退出loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

我们再来看一下内部函数中loop部分的实现。代码可能比较多,虽然我已经尽量删了很多条件编译了...

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();
    
    // 判断RunLoop和RunLoopMode是否停止,提前返回
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    // 这部分可以不看
#if __HAS_DISPATCH__
    __CFPort dispatchPort = CFPORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#endif
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    
#if __HAS_DISPATCH__
    dispatch_source_t timeout_timer = NULL;
#endif
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
#if __HAS_DISPATCH__
        dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        timeout_context->ds = timeout_timer;
#endif
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
#if __HAS_DISPATCH__
        dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
#endif
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
    // 下面开始,上面一堆大概意思就是用dispatch来管理
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {

        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;

        uint8_t msg_buffer[3 * 1024];

        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;

        __CFPortSet waitSet = rlm->_portSet;
        
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        // 2. 通知Observers:RunLoop即将触发Timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        }
        
        // 3. 通知Observers:RunLoop即将触发Source0(非port)回调
        if (rlm->_observerMask & kCFRunLoopBeforeSources) {
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        }
        // 执行被加入的block
        __CFRunLoopDoBlocks(rl, rlm);
      
        // 4. RunLoop触发Source0(非port)回调
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        // 执行被加入的block
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // 5. 如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息
#if __HAS_DISPATCH__
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
                goto handle_msg;
            }
        }
#endif
        
        didDispatchPortLastTime = false;
        // 6. 通知Observers: RunLoop的线程即将进入休眠。
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
#if __HAS_DISPATCH__
        __CFPortSetInsert(dispatchPort, waitSet);
#endif
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            msg = (mach_msg_header_t *)msg_buffer;
            /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
            /// • 一个基于port的Source的事件
            /// • 一个Timer到时间了
            /// • RunLoop自身的超时时间到了
            /// • 被其他什么调用者手动唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
            
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
#else
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm);
#endif
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
      
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        
#if __HAS_DISPATCH__
        __CFPortSetRemove(dispatchPort, waitSet);
#endif
        __CFRunLoopSetIgnoreWakeUps(rl);  
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
      
        // 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        // 9. 收到消息,处理消息。
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_NOTHING, rl, rlm, livePort, 0);
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_WAKEUP, rl, rlm, livePort, 0);
            // do nothing on Mac OS
        }
        // 9.1 如果一个Timer到时间了,触发这个Timer的回调
        // __CFRunLoopDoTimers
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_TIMER, rl, rlm, livePort, 0);
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                rlm->_timerSoftDeadline = UINT64_MAX;
                rlm->_timerHardDeadline = UINT64_MAX;
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        
        /* --- DISPATCHES  --- */
        // 9.2 如果有dispatch到main_queue的block,执行block
        // __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
#if __HAS_DISPATCH__
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_DISPATCH, rl, rlm, livePort, 0);
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
            cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_START, rl, rlm, msg, livePort);
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_DISPATCH | DBG_FUNC_END, rl, rlm, msg, livePort);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        }
#endif
        
        /* --- SOURCE1S  --- */
        // 9.3 如果一个Source1(基于port)发出事件了,处理这个事件
        else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            cf_trace(KDEBUG_EVENT_CFRL_WAKEUP_FOR_SOURCE, rl, rlm, 0, 0);
            // 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);
                }
            } else {
                os_log_error(_CFOSLog(), "__CFRunLoopModeFindSourceForMachPort returned NULL for mode '%@' livePort: %u", rlm->_name, livePort);
            }
        }
        
        /* --- BLOCKS --- */
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        // 执行加入到Loop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        //确定返回值
        if (sourceHandledThisLoop && stopAfterHandle) {
            // 进入loop时参数说处理完事件就返回
            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)) {
            // source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished;
        }
        // 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (0 == retVal);
    // 释放
#if __HAS_DISPATCH__
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else
#endif
    {
        free(timeout_context);
    }
    return retVal;
}

可以看到,实际上RunLoop就是这样一个函数,其内部是一个do-while循环。当你调用CFRunLoopRun()时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

5. RunLoop的底层实现

从上面代码可以看到,RunLoop的核心是基于mach port的,其进入休眠时调用的函数是mach_msg()

为了解释这个逻辑,下面稍微介绍一下 SX/iOS的系统架构。

系统架构

苹果官方将整个系统大致划分为上述4个层次:

  • 应用层包括用户能接触到的图形应用,例如Spotlight、Aqua、SpringBoard等。
  • 应用框架层即开发人员接触到的Cocoa等框架。
  • 核心框架层包括各种核心框架、OpenGL等内容。
  • Darwin即操作系统的核心,包括系统内核、驱动、Shell 等内容,这一层是开源的,其所有源码都可以在 opensource.apple.com 里找到。

我们在深入看一下Darwin这个核心的架构:

Darwin架构

其中,在硬件层上面的三个组成部分:Mach、BSD、IOKit(还包括一些上面没标注的内容),共同组成了XNU内核。

  • XNU内核的内环被称作Mach,其作为一个微内核,仅提供了诸如处理器调度、IPC (进程间通信)等非常少量的基础服务。
  • BSD层可以看作围绕Mach层的一个外环,其提供了诸如进程管理、文件系统和网络等功能。
  • IOKit层是为设备驱动提供了一个面向对象(C++)的一个框架。

Mach本身提供的 API 非常有限,而且苹果也不鼓励使用Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。在Mach中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。和其他架构不同, Mach的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。”消息”是Mach中最基础的概念,消息在两个端口 (port) 之间传递,这就是Mach的IPC(进程间通信)的核心。

Mach消息

Mach的消息定义是在 <mach/message.h> 头文件的,很简单:

typedef struct{
    mach_msg_header_t       header;
    mach_msg_body_t         body;
} mach_msg_base_t;

typedef struct{
    mach_msg_bits_t       msgh_bits;
    mach_msg_size_t       msgh_size;
    mach_port_t           msgh_remote_port;
    mach_port_t           msgh_local_port;
    mach_port_name_t      msgh_voucher_port;
    mach_msg_id_t         msgh_id;
} mach_msg_header_t;

typedef struct{
    mach_msg_size_t msgh_descriptor_count;
} mach_msg_body_t;

一条Mach消息实际上就是一个二进制数据包 (BLOB),其头部定义了当前端口local_port和目标端口remote_port
发送和接受消息是通过同一个API进行的,其option标记了消息传递的方向:

mach_msg_return_t 
  mach_msg(mach_msg_header_t *msg,
           mach_msg_option_t option,
           mach_msg_size_t send_size,
           mach_msg_size_t rcv_size,
           mach_port_name_t rcv_name,
           mach_msg_timeout_t timeout,
           mach_port_name_t notify);

为了实现消息的发送和接收,mach_msg()函数实际上是调用了一个Mac 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在Mach中等同于系统调用。当你在用户态调用mach_msg_trap()时会触发陷阱机制,切换到内核态;内核态中内核实现的mach_msg()函数会完成实际的工作,如下图:

mach msg trap

这些概念可以参考维基百科: System_callTrap_(computing)

RunLoop的核心就是一个mach_msg() (见上面代码的第7步),RunLoo调用这个函数去接收消息。如果没有别人发送 port消息过来,内核会将线程置于等待状态。

例如在模拟器里跑起一个iOS的App,然后在App静止时点击暂停,你会看到主线程调用栈是停留在mach_msg_trap()这个地方。

关于具体的如何利用mach port发送信息,可以看看 NSHipster 这一篇文章,或者这里的中文翻译 。

关于Mach的历史可以看看这篇很有趣的文章:Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian

6. 苹果用RunLoop实现的功能

首先我们可以看一下App启动后RunLoop的状态:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    NSRunLoop *runloop = [NSRunLoop mainRunLoop];
    NSLog(@"\n%@", runloop);
    return YES;
}

//输出
<CFRunLoop 0x600000e7c000 [0x7fff80617cb0]>{wakeup port = 0x1903, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600003c26df0 [0x7fff80617cb0]>{type = mutable set, count = 2,
entries =>
    0 : <CFString 0x7fff867f6c40 [0x7fff80617cb0]>{contents = "UITrackingRunLoopMode"}
    2 : <CFString 0x7fff8062b0a0 [0x7fff80617cb0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600003c7c3c0 [0x7fff80617cb0]>{type = mutable set, count = 14,
entries =>
    0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
    1 : <CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}
    3 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
    5 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
    6 : <CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <0x7fcb05001038>
)}}
    7 : <CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}
    8 : <CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}
    9 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
    10 : <CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <0x7fcb05001038>
)}}
    13 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
    14 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
    15 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
    18 : <CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}
    21 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
}
,
modes = <CFBasicHash 0x600003c26d90 [0x7fff80617cb0]>{type = mutable set, count = 3,
entries =>
    0 : <CFRunLoopMode 0x6000009740d0 [0x7fff80617cb0]>{name = UITrackingRunLoopMode, port set = 0x2c03, queue = 0x600001c7c300, source = 0x600001c7c500 (not fired), timer port = 0x2d03, 
    sources0 = <CFBasicHash 0x600003c7c420 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
    1 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
    2 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
    6 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
}
,
    sources1 = <CFBasicHash 0x600003c7c450 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
    3 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
    4 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
    5 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
    6 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}",
    "<CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}",
    "<CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
    "<CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
    "<CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}"
),
    timers = (null),
    currently 606712521 (267350640984313) / soft deadline in: 1.84464767e+10 sec (@ -1) / hard deadline in: 1.84464767e+10 sec (@ -1)
},

    1 : <CFRunLoopMode 0x6000009741a0 [0x7fff80617cb0]>{name = GSEventReceiveRunLoopMode, port set = 0x5303, queue = 0x600001c7c280, source = 0x600001c7c680 (not fired), timer port = 0x2f03, 
    sources0 = <CFBasicHash 0x600003c7c4e0 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
    0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
}
,
    sources1 = <CFBasicHash 0x600003c7c510 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
    1 : <CFRunLoopSource 0x600000770180 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
}
,
    observers = (null),
    timers = (null),
    currently 606712521 (267350643730266) / soft deadline in: 1.84464767e+10 sec (@ -1) / hard deadline in: 1.84464767e+10 sec (@ -1)
},

    2 : <CFRunLoopMode 0x60000097c270 [0x7fff80617cb0]>{name = kCFRunLoopDefaultMode, port set = 0x2303, queue = 0x600001c70800, source = 0x600001c70900 (not fired), timer port = 0x2103, 
    sources0 = <CFBasicHash 0x600003c7c480 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x6000007700c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff384c19f5)}}
    1 : <CFRunLoopSource 0x60000077c3c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000770480, callout = __handleEventQueue (0x7fff48126d97)}}
    2 : <CFRunLoopSource 0x6000007783c0 [0x7fff80617cb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x600001664a80, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff3662186a)}}
    6 : <CFRunLoopSource 0x60000077c540 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600003c7cde0, callout = __handleHIDEventFetcherDrain (0x7fff48126da3)}}
}
,
    sources1 = <CFBasicHash 0x600003c7c4b0 [0x7fff80617cb0]>{type = mutable set, count = 4,
entries =>
    3 : <CFRunLoopSource 0x600000770240 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x3007, callout = PurpleEventCallback (0x7fff384c1a01)}}
    4 : <CFRunLoopSource 0x6000007758c0 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c4b0> {port = 4007, callback = 0x0}}
    5 : <CFRunLoopSource 0x600000775980 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000326e4c0> {port = 5507, callback = 0x7fff2e3f7d2b}}
    6 : <CFRunLoopSource 0x600000775800 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600003c1c450> {port = 430b, callback = 0x0}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}",
    "<CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}",
    "<CFRunLoopObserver 0x600000370640 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff480bc2eb), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
    "<CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x6000003706e0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff480bc354), context = <CFRunLoopObserver context 0x7fcb03401d10>}",
    "<CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fcb05001038>\n)}}"
),
    timers = <CFArray 0x600001674960 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x600000774540 [0x7fff80617cb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 606712522 (1.01609397 @ 267351663431855), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x7fff2576c7c2 / 0x7fff475e21aa) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore), context = <CFRunLoopTimer context 0x600002720f40>}
)},
    currently 606712521 (267350643809969) / soft deadline in: 1.01962186 sec (@ 267351663431855) / hard deadline in: 1.01962181 sec (@ 267351663431855)
},

}
}

可以看到App运行到didFinishLaunchingWithOptions时,系统默认注册了3个Mode:

  1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个Mode下运行的。
  2. UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响。
  3. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

我们常提到的还有两个Mode:

  1. kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
  2. UIInitializationRunLoopMode: 在刚启动App时第进入的第一个Mode,启动完成后就不再使用。

kCFRunLoopCommonModes会在之后加入,如果在某个ViewController内部打印时会看到。

UIInitializationRunLoopMode无论是main函数中,或者下RunLoop内部函数的符号断点都没看到过。

如果有人知道怎么回事望告知。

当RunLoop进行回调时,一般都是通过一个很长的函数调用出去 (call out)。当在代码中下断点调试时,通常能在调用栈上看到这些函数。

下面是这几个函数的整理版本,如果在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:

{
    /// 1. 通知Observers,即将进入RunLoop
    /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
    do {
        /// 2. 通知 Observers: 即将触发 Timer 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
        /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 4. 触发 Source0 (非基于port的) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 6. 通知Observers,即将进入休眠
        /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
 
        /// 7. sleep to wait msg.
        mach_msg() -> mach_msg_trap();
 
        /// 8. 通知Observers,线程被唤醒
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
 
        /// 9.1 如果是被Timer唤醒的,回调Timer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
 
        /// 9.2 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
 
        /// 9.3 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
    } while (...);
 
    /// 10. 通知Observers,即将退出RunLoop
    /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

6.1 AutoreleasePool

App启动后,苹果在主线程RunLoop里注册了两个Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()

// activities = 0xa0 BeforeWaiting | Exit
6 : <CFRunLoopObserver 0x600000370820 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <0x7fcb05001038>
)}}
// activities = 0x1 Entry
10 : <CFRunLoopObserver 0x600000370780 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x600003c18990 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (
    0 : <0x7fcb05001038>
)}}

第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush()创建自动释放池。其order是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

第二个 Observer 监视了两个事件:

  • BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池。

  • Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()来释放自动释放池。这个Observer的order是2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop创建好的AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建Pool了。

6.2 事件响应

苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()

我们可以下一个``__IOHIDEventSystemClientQueueCallback`符号断点:

__IOHIDEventSystemClientQueueCallback
// 处理Source1的事件
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls)
{
    
    CHECK_FOR_FORK();
    Boolean sourceHandled = false;

    /* Fire a version 1 source */
    CFRetain(rls);
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
    __CFRunLoopSourceUnsetSignaled(rls);
    __CFRunLoopSourceUnlock(rls);
        __CFRunLoopDebugInfoForRunLoopSource(rls);
        void *perform = rls->_context.version1.perform;
        void *info = rls->_context.version1.info;
        cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE1 | DBG_FUNC_START, rl, rlm, perform, info);
        // 执行回调函数
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(perform, info);
...

当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由IOKit.framework生成一个IOHIDEvent件并由SpringBoard接收。这个过程的详细情况可以参考这里

SpringBoard只接收按键(锁屏/静音等)、触摸、加速、接近传感器等几种Event,随后用mach port转发给需要的App进程。随后苹果注册的那个Source1就会触发回调,并调用_UIApplicationHandleEventQueue()进行应用内部的分发。

_UIApplicationHandleEventQueue()会把IOHIDEvent处理并包装成UIEvent进行处理或分发,其中包括识别UIGesture、处理屏幕旋、转发送给UIWindow等。通常事件比如UIButton点击、touchesBegin/Move/End/Cancel事件都是在这个回调中完成的。

6.3手势识别

当上面的_UIApplicationHandleEventQueue()识别了一个手势时,其首先会调用Cancel将当前的 touchesBegin/Move/End列回调打断。随后系统将对应的UIGestureRecognizer标记为待处理。

    1 : <CFRunLoopObserver 0x600000374140 [0x7fff80617cb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff47c2f06a), context = <CFRunLoopObserver context 0x600001974310>}

苹果注册了一个Observer监测BeforeWaiting (Loop即将进入休眠)事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的GestureRecognizer,并执行GestureRecognizer的回调。

当有UIGestureRecognizer的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

6.4 界面更新

当在操作UI时,比如改变了frame、更新了UIView/CALayer的层次时,或者手动调用了UIView/CALayer的 setNeedsLayout/setNeedsDisplay方法后,这个UIView/CALayer就被标记为待处理,并被提交到一个全局的容器去。

苹果注册了一个Observer监听BeforeWaiting(即将进入休眠)和Exit(即将退出Loop)事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()

这个函数里会遍历所有待处理的UIView/CAlayer以执行实际的绘制和调整,并更新UI界面。

18 : <CFRunLoopObserver 0x600000378a00 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b0c046e), context = <CFRunLoopObserver context 0x0>}

这个函数内部的调用栈大概是这样的:

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
    QuartzCore:CA::Transaction::observer_callback:
        CA::Transaction::commit();
            CA::Context::commit_transaction();
                CA::Layer::layout_and_display_if_needed();
                    CA::Layer::layout_if_needed();
                        [CALayer layoutSublayers];
                            [UIView layoutSubviews];
                    CA::Layer::display_if_needed();
                        [CALayer display];
                            [UIView drawRect];

6.5 定时器

  • NSTimer

    其实就是CFRunLoopTimerRef,他们之间是toll-free bridged的。一个NSTimer注册到 RunLoop 后,RunLoop会为其重复的时间点注册好事件。

    • RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer有个属性叫做Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。

    • 错过时间点的回调不会执行。

      如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。

  • CADisplayLink

    是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和NSTimer并不一样,其内部实际是操作了一个 Source)。

    如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook开源的AsyncDisplayLink就是为了解决界面卡顿的问题,其内部也用到了RunLoop。

6.6 PerformSelecter

当调用NSObject的performSelecter:afterDelay:,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中。所以如果当前线程没有 RunLoop,则这个方法会失效

6.7 关于GCD

实际上RunLoop底层也会用到GCD的东西,比如 RunLoop是用dispatch_source_t实现的Timer。或者XNU内核的mk_timer。但同时 GCD 提供的某些接口也用到了RunLoop, 例如dispatch_async()

#if TARGET_OS_MAC || TARGET_OS_WIN32
#define USE_DISPATCH_SOURCE_FOR_TIMERS __HAS_DISPATCH__
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif

当调用dispatch_async(dispatch_get_main_queue(), block)时,libDispatch会向主线程的RunLoop发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()里执行这个 block。

但这个逻辑仅限于dispatch到主线程,dispatch到其他线程仍然是由libDispatch处理的。

6.8 关于网络请求

iOS 中,关于网络请求的接口自下至上有如下几层:

CFSocket
CFNetwork       ->ASIHttpRequest
NSURLConnection ->AFNetworking
NSURLSession    ->AFNetworking2, Alamofire
  • CFSocket是最底层的接口,只负责socket通信。

  • CFNetwork是基于CFSocket等接口的上层封装。

    ASIHttpRequest工作于这一层。

  • NSURLConnection是基于CFNetwork的更高层的封装,提供面向对象的接口。

    AFNetworking工作于这一层。

  • NSURLSession是iOS7中新增的接口,表面上是和NSURLConnection并列的,但底层仍然用到了NSURLConnection的部分功能 (比如com.apple.NSURLConnectionLoader线程)。

    AFNetworking2Alamofire工作于这一层。

下面主要介绍下NSURLConnection的工作过程。

通常使用NSURLConnection时,你会传入一个Delegate,当调用了[connection start]后,这个Delegate就会不停收到事件回调。实际上,start这个函数的内部会获取CurrentRunLoop,然后在其中的DefaultMode添加了4个 Source0 (即需要手动触发的Source)

当开始网络传输时,我们可以看到NSURLConnection创建了两个新线程:

  • com.apple.NSURLConnectionLoader

    内部会使用RunLoop来接收底层socket的事件,并通过之前添加的Source0通知到上层的Delegate。

  • com.apple.CFSocket.private

    处理底层 socket 连接的。

NSURLConnection

CFMultiplexerSource是负责各种Delegate回调的,CFHTTPCookieStorage是处理各种Cookie的。

NSURLConnectionLoader中的RunLoop通过一些基于mach portSource1接收来自底层CFSocket的通知。当收到通知后,其会在合适的时机向CFMultiplexerSourceSource0发送通知,同时主动唤醒Delegate线程的RunLoop来让其处理这些通知。CFMultiplexerSource会在Delegate线程的RunLoop对Delegate执行实际的回调。

7. RunLoop的实际应用举例

7.1 AFNetworking

AFURLConnectionOperation 这个类是基于NSURLConnection构建的,其希望能在后台线程接收Delegate回调。为此AFNetworking单独创建了一个线程,并在这个线程中启动了一个RunLoop:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
 
+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}

RunLoop启动前内部必须要有至少一个Timer/Observer/Source,所以AFNetworking[runLoop run]之前先创建了一个新的NSMachPort添加进去了。通常情况下,调用者需要持有这个NSMachPort (mach_port)并在外部线程通过这个port发送消息到loop内;但此处添加port只是为了让RunLoop不至于退出,并没有用于实际的发送消息。

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

当需要这个后台线程执行任务时,AFNetworking通过调用[NSObject performSelector:onThread:..]将这个任务扔到了后台线程的RunLoop中。

7.2 AsyncDisplayKit

AsyncDisplayKit 是Facebook推出的用于保持界面流畅性的框架,其原理大致如下:

UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版、绘制、UI对象操作

  • 排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。
  • 绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。
  • UI对象操作通常包括UIView/CALayer等UI对象的创建、设置属性和销毁。

其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成,并且有时后面的操作需要依赖前面操作的结果(例如TextView创建时可能需要提前计算出文本的大小)。AsyncDisplayKit所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟(例如视图的创建、属性的调整)。

为此,AsyncDisplayKit创建了一个名为ASDisplayNode的对象,并在内部封装了UIView/CALayer,它具有和UIView/CALayer相似的属性,例如frame、backgroundColor等。所有这些属性都可以在后台线程更改,开发者可以只通过Node来操作其内部的UIView/CALayer,这样就可以将排版和绘制放入了后台线程。但是无论怎么操作,这些属性总需要在某个时刻同步到主线程的UIView/CALayer去。

AsyncDisplayKit仿照QuartzCore/UIKit框架的模式,实现了一套类似的界面更新的机制:

在主线程的RunLoop中添加一个Observer,监听了kCFRunLoopBeforeWaiting和kCFRunLoopExit事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。

具体的代码可以看这里:_ASAsyncTransactionGroup

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容