Runloop学习笔记

1、什么是Runloop?Runloop线程什么关系?

直接参考官方文档。
Runloop跟线程是一一对应的关系,底层通过一个哈希表来进行映射存储,线程为key,runloop为value。主线程的Runloop在app启动时自动创建,然后存到哈希表中。子线程在我们需要的时候系统会帮我们创建,存入表中,下次直接读取,保证一个线程只有一个runloop。

2、Runloop解剖

2.1、Runloop底层结构

2.1.1、整体结构概览

运行循环(Runloop)从两种不同类型的源(Source)接收事件(Event)。输入源(Input source)传递异步事件,通常是来自另一个线程或不同应用程序的消息。定时器源(Timer sources)提供同步事件,这些事件以预定的时间或重复的间隔发生。两种类型的源都使用应用程序特定的处理例程在事件到达时进行处理。
下图显示了运行循环的概念结构和各种来源。输入源将异步事件传递给相应的处理程序,并导致runUntilDate:方法(在线程的关联NSRunLoop对象上调用)退出。计时器源将事件交付给它们的处理例程,但不会导致运行循环退出。


Runloop-来自官方文档.jpg

除了处理输入源之外,运行循环还生成关于运行循环行为的通知。已注册的运行循环观察者(Observer)可以接收这些通知,并使用它们在线程上执行额外的处理。可以使用Core Foundation在相应的线程上注册Runloop观察者。

2.1.2、底层数据结构
  • Runloop的底层结构__CFRunLoop
    Runloop本身也是个对象,底层结构是个结构体(__CFRunLoop):
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;
};

关键属性解析:

_pthread:当前线程
_commonModes common:模式,是个集合
_currentMode:当前运行的模式
_modes:所有模式

一个Runloop可以关联多个Mode,但是每次只能在一个Mode下运行。

  • 模式的底层结构__CFRunLoopMode
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 */
};

关键属性解析:

_sources0:非基于Port的输入源,一般是app内部事件源,包括触摸事件等;
_sources1:基于Port的输入源,一般是线程间、进程间通信的源。
_observers:观察者,可以监听Runloop的运行状态
_timers:也叫计时器源

一个Mode可以同时关联多个源,Runloop只有在当前模式运行时才会执行其关联的源传递的事件。

  • Runloop整体结构图


    runloop结构.jpg

下面几节提供有关运行循环的组件及其运行模式(Mode)的更多信息。它们还描述了事件处理期间在不同时间生成的通知。

2.2、模式(Run Loop Modes)

Mode是要监听的输入源和计时器的集合,以及要通知的运行循环观察者的集合。每次运行Runloop时,都要指定(显式或隐式)要运行的特定“模式”。在运行循环的该过程中,只有与该模式相关联的源被监听并允许交付其事件(类似地,只有与该模式相关联的观察者才会被通知运行循环的进程)。与其他模式关联的源将保留新加入的事件,直到后续事件以相应的模式通过循环。

模式实际上就是一个字符串名称。我们可以自定义。系统同为我们定义了一些模式:

NSDefaultRunLoopMode (Cocoa)
kCFRunLoopDefaultMode (Core Foundation)
NSEventTrackingRunLoopMode (Cocoa)
NSRunLoopCommonModes (Cocoa)
kCFRunLoopCommonModes (Core Foundation)
NSConnectionReplyMode (Cocoa)
NSModalPanelRunLoopMode (Cocoa)

我们常用的是Default和Tracking(scrollview滚动时),Common是一组常用模式的可配置组。将输入源与此模式关联也将其与组中的每个模式关联。对于Cocoa应用程序,该集合默认包括默认、模式和事件跟踪模式。

备注:模式区分基于事件的来源,而不是事件的类型。

2.3、输入源(Input Sources)

输入源将事件异步地交付给线程。事件的源取决于输入源的类型,通常是两种类型。其一是基于端口(Port-Based Sources)的输入源,监听应用程序的Mach端口;其二自定义输入源监听自定义事件源。就您的Runloop而言,输入源是基于端口的还是自定义的并不重要。系统通常都实现两种类型的输入源,您可以按原样使用它们。这两个信号源之间的唯一区别是它们发出信号的方式。基于端口的源由内核自动发出信号,而自定义源必须从另一个线程手动发出信号。

当你创建一个输入源时,你可以将它分配给运行循环的一个或多个模式。模式影响在任何给定时刻监听的输入源。大多数情况下,在默认模式下运行Runloop,但您也可以指定自定义模式。如果输入源不在当前监视的模式中,则它生成的任何事件都将被保存,直到运行循环以正确的模式运行为止。

以下部分将描述一些输入源。

2.3.1、基于端口的源(Port-Based Sources)

Cocoa和Core Foundation为使用与端口相关的对象和函数创建基于端口的输入源提供了内置支持。例如,在Cocoa中,你根本不需要直接创建输入源。你只需创建一个端口对象,并使用NSPort的方法将该端口添加到Runloop中。端口对象为你处理所需输入源的创建和配置。

在Core Foundation中,必须手动创建端口及其运行循环源。在这两种情况下,要使用与端口不透明类型(CFMachPortRef、CFMessagePortRef或CFSocketRef)相关联的函数来创建适当的对象。

有关如何设置和配置基于端口的自定义源的示例,参考如下代码:

     self.workThread = [[NSThread alloc] initWithBlock:^{
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        NSPort *port = [[NSPort alloc] init];
        [runloop addPort:port forMode:NSRunLoopCommonModes];
        [runloop run];
    }];
    [self.workThread start];
2.3.2、自定义源(Custom Input Sources)

要创建自定义输入源,必须使用Core Foundation中与CFRunLoopSourceRef 不透明类型关联的函数。你可以使用多个回调函数配置自定义输入源。Core Foundation在不同的点调用这些函数来配置源,处理任何传入的事件,并在从运行循环中删除源时销毁源。

除了定义事件到达时自定义源的行为外,还必须定义事件传递机制。源的这一部分在一个单独的线程上运行,负责向输入源提供它的数据,并在数据准备好进行处理时向它发出信号。事件交付机制由您决定,但不必过于复杂。

有关如何创建自定义输入源的示例,请参考如下代码:

    CFRunLoopSourceContext context = {
        0,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        schedule,
        cancel,
        perform,
    };
    /**
     
     参数一:传递NULL或kCFAllocatorDefault以使用当前默认分配器。
     参数二:优先级索引,指示处理运行循环源的顺序。这里我传0为了的就是自主回调
     参数三:为运行循环源保存上下文信息的结构
     */
    CFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    // source --> runloop 指定了mode  那么此时我们source就进入待绪状态
    CFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode);
    // 一个执行信号
    CFRunLoopSourceSignal(source0);
    // 唤醒 run loop 防止沉睡状态
    CFRunLoopWakeUp(rlp);
    // 取消 移除
    CFRunLoopRemoveSource(rlp, source0, kCFRunLoopDefaultMode);
    CFRelease(rlp);

void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"准备代发");
}

void perform(void *info){
    NSLog(@"执行");
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode){
    NSLog(@"取消了");
}

2.3.3、基于performSelector:的源(Cocoa Perform Selector Sources)

除了基于端口的源之外,Cocoa还定义了一个自定义输入源,允许你在任何线程上执行选择器。与基于端口的源类似,执行选择器请求在目标线程上序列化,这减轻了在一个线程上运行多个方法时可能出现的许多同步问题。与基于端口的源不同,执行选择器源在执行选择器之后将自己从运行循环中移除。

相关方法:

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

2.4、计时器源(Timer Sources)

计时器源在未来的预设时间同步地向线程交付事件。计时器是线程通知自己做某事的一种方式。

尽管计时器生成基于时间的通知,但它不是一种实时机制。像输入源一样,计时器与运行循环的特定模式相关联。如果一个计时器不在当前被运行循环监视的模式中,那么它不会被触发,直到你在一个计时器支持的模式中运行运行循环。类似地,如果在Runloop正在执行一个处理例程时触发一个计时器,那么计时器将等待到下一次通过Runloop调用它的处理例程。如果Runloop根本没有运行,则计时器永远不会触发。

你可以将计时器配置为只生成一次或重复事件。重复计时器根据预定的触发时间而不是实际的触发时间自动重新调度自己。例如,如果一个计时器被计划在特定时间触发,并且之后每隔5秒触发一次,那么计划的触发时间将始终落在最初的5秒时间间隔上,即使实际的触发时间被延迟了。如果触发时间被延迟到错过了一个或多个预定的触发时间,则在错过的时间段只触发一次计时器。在触发错过的时间段之后,计时器将被重新调度到下一个预定的触发时间。

有关配置定时器源的更多信息,请参见配置定时器源。有关参考如下代码:

    self.workThread = [[NSThread alloc] initWithBlock:^{
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1  target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [runloop addTimer:timer forMode:NSRunLoopCommonModes];
        [runloop run];
    }];
    [self.workThread start];

2.5、观察者(Run Loop Observers)

源在适当的异步或同步事件发生时触发,与之相反,运行循环观察者在运行循环本身执行期间在特定位置触发。你可以使用运行循环观察器来准备线程以处理给定的事件,或者在线程进入睡眠状态之前准备线程。你可以将运行循环观察者与运行循环中的以下事件关联起来:

开始进入Runloop(kCFRunLoopEntry)。
当Runloop要处理一个计时器时(kCFRunLoopBeforeTimers)。
当Runloop要处理输入源时(kCFRunLoopBeforeSources)。
当Runloop即将进入睡眠状态时(kCFRunLoopBeforeWaiting)。
当Runloop已经唤醒,但在它处理唤醒它的事件之前(kCFRunLoopAfterWaiting)。
从Runloop中退出(kCFRunLoopExit)。

你可以使用Core Foundation添加Runloop观察者到应用程序中。要创建一个运行循环观察者,您需要创建一个CFRunLoopObserverRef 不透明类型的新实例。这种类型跟踪你的自定义回调函数和它感兴趣的活动。

与计时器类似,运行循环观察者可以使用一次或多次。一次性观察者在触发后将自己从运行循环中移除,而重复观察者则保持连接。你可以指定在创建观察器时运行一次还是重复。

有关如何创建运行循环观察器的示例,请参考一下代码:

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &mainObserverCallBack, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }

void mainObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    
    ViewController *obj = (__bridge id)info;
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"开始循环");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"将要执行timer");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"将要执行输入源");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"将要进入休眠");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop已被唤醒");
            break;
        case kCFRunLoopExit:
            NSLog(@"runloop退出");
            break;
        case kCFRunLoopAllActivities:
            NSLog(@"状态更新了");
            break;
        default:
            break;
    }
}

mainObserverCallBack是回调函数,在回调函数可以收到状态变化通知。

3、Runloop工作原理

3.1、事件的运行循环序列(The Run Loop Sequence of Events)

每次运行它时,线程的运行循环都会处理挂起事件,并为任何附加的观察者生成通知。它这样做的顺序非常具体,如下图(参考自网络):


Runloop循环.png

1、通知观察者(Observer)已进入运行循环。
2、通知观察者(Observer)即将被触发已经就绪的计时器。
3、通知观察者(Observer)即将被触发非基于端口的输入源。
4、处理非基于端口的输入源(Source0)。
5、如果有基于端口的输入源(Source1)已经准备好并等待触发,则立即处理该事件。执行步骤9。
6、通知观察者(Observer)线程即将进入睡眠状态。
7、将线程置于睡眠状态,调用 mach_msg 等待接受 mach_port 的消息,直到发生以下事件之一:

  • 基于端口的输入源(Source1)收到新的事件。
  • 一个计时器的触发时间到了。
  • 为Runloop设置的超时值过期。
  • Runloop被其他调用者显式唤醒。

8、通知观察者(Observer)线程刚被唤醒。
9、处理挂起事件。

  • 如果用户设定的计时器触发了时间到了,则处理计时器事件并重新启动循环。跳转到步骤2。
  • 如果有dispatch到main_queue的block,执行block。
  • 如果一个基于端口的输入源(Source1)被触发了,则处理这个事件。
  • 如果Runloop被显式唤醒,但尚未超时,则重新启动循环。跳转转步骤2。

10、通知观察者(Observer)Runloop退出。

源码解析

  • Runloop相关状态状态
/* Reasons for CFRunLoopRunInMode() to Return */
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

  • void CFRunLoopRun(void)
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  • CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根据modeName找到本次运行的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环
    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);
    //取上一次运行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //如果本次mode和上次的mode一致
    rl->_currentMode = currentMode;
    //初始化一个result为kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;
    
    if (currentMode->_observerMask & kCFRunLoopEntry )
        /// 1. 通知 Observers: RunLoop 即将进入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit )
        /// 10. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
  • __CFRunLoopRun
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    //获取系统启动后的CPU运行时间,用于控制超时时间
    uint64_t startTSR = mach_absolute_time();
    
    // 判断当前runloop的状态是否关闭
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    
    //mach端口,在内核中,消息在端口之间传递。 初始为0
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    //判断是否为主线程
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    //如果在主线程 && runloop是主线程的runloop && 该mode是commonMode,则给mach端口赋值为主线程收发消息的端口
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        //mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CF
        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
    
    dispatch_source_t timeout_timer = NULL;
    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;
        // 1.0e10 == 1* 10^10
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        //seconds为超时时间,超时时执行__CFRunLoopTimeout函数
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        timeout_context->ds = timeout_timer;
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        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);
    } else { // infinite timeout
        //永不超时 - 永动机
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
    //标志位默认为true
    Boolean didDispatchPortLastTime = true;
    //记录最后runloop状态,用于return
    int32_t retVal = 0;
    
    // itmes
 
    do {
        //初始化一个存放内核消息的缓冲池
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
        //取所有需要监听的port
        __CFPortSet waitSet = rlm->_portSet;
        
        //设置RunLoop为可以被唤醒状态
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        /// 执行被加入的block
        __CFRunLoopDoBlocks(rl, rlm);
        /// 4. RunLoop 触发 Source0 (非port) 回调。
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            /// 执行被加入的block
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        //如果没有Sources0事件处理 并且 没有超时,poll为false
        //如果有Sources0事件处理 或者 超时,poll都为true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        //第一次do..whil循环不会走该分支,因为didDispatchPortLastTime初始化是true
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            //从缓冲区读取消息
            msg = (mach_msg_header_t *)msg_buffer;
            /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                //如果接收到了消息的话,前往第9步开始处理msg
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }
        
        didDispatchPortLastTime = false;
        /// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //设置RunLoop为休眠状态
        __CFRunLoopSetSleeping(rl);
        // do not do any user callouts after this point (after notifying of sleeping)
        
        // Must push the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced.
        
        __CFPortSetInsert(dispatchPort, waitSet);
        
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        
        //这里有个内循环,用于接收等待端口的消息
        //进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
        do {
            if (kCFUseCollectableAllocator) {
                objc_clear_stack(0);
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            
            msg = (mach_msg_header_t *)msg_buffer;
            //7.接收waitSet端口的消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            //收到消息之后,livePort的值为msg->msgh_local_port,
            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
        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
        /// • 一个基于 port 的Source 的事件。
        /// • 一个 Timer 到时间了
        /// • RunLoop 自身的超时时间到了
        /// • 被其他什么调用者手动唤醒
        
        // mach 事务 - 指令 
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
        
        
#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        
        // Must remove the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced. Also, we don't want them left
        // in there if this function returns.
        
        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        // user callouts now OK again
        //取消runloop的休眠状态
        __CFRunLoopUnsetSleeping(rl);
        /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        /// 收到消息,处理消息。
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
#if DEPLOYMENT_TARGET_WINDOWS
        if (windowsMessageReceived) {
            // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            if (rlm->_msgPump) {
                rlm->_msgPump();
            } else {
                MSG msg;
                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            
            // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
            // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
            // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
            __CFRunLoopSetSleeping(rl);
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            __CFRunLoopUnsetSleeping(rl);
            // If we have a new live port then it will be handled below as normal
        }
        
        
#endif
        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
#if DEPLOYMENT_TARGET_WINDOWS
            // Always reset the wake up port, or risk spinning forever
            ResetEvent(rl->_wakeUpPort);
#endif
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
            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();
            // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        /// 9.2 如果有dispatch到main_queue的block,执行block
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                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);
                }
#elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
            }
        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        
        /// 执行加入到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 (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    
    return retVal;
}

你可能感兴趣的:(Runloop学习笔记)