RunLoop 运行机制原理逻辑与GCD及线程关系剖析

前言

文章主要会RunLoop源码进行剖析,里面会有对它的理解及注释,有不足望见解

1,RunLoop是什么?

广义上的来说,run loop 就是所谓的 event loop,或者称之为「事件循环」或者「事件分发器」。Event loop 是 event-driven programming(事件驱动编程)非常重要的组成部分,而事件驱动编程则是 GUI 程序的最常见编程方式(现在似乎在服务器端也有很多应用,但在 GUI 编程方面肯定是绕不过去)。

Event Loop

Run Loop 是一个 iOS 开发里的基础概念,它并非独有的机制,很多系统和框架都有类似的实现,Run Loop 是 Event Loop (事件循环)机制的在 iOS 平台的一种实现。

Event loop 的思想非常简单,用下面的伪代码来表示:

int main(void){

    初始化();

    while(message !=退出){

        处理事件(message);

        message =获取下一个事件();

    }

    return 0;

}

苹果官网文档:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

Run loop management is not entirely automatic. You must still design your thread’s code to start the run loop at appropriate times and respond to incoming events. Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop. Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.

The following sections provide more information about run loops and how you configure them for your application. For additional information about run loop objects, see NSRunLoop Class Reference and CFRunLoop Reference.

运行循环是与线程相关的基础架构的一部分。一个运行循环是指用于安排工作,并协调接收传入事件的事件处理循环。运行循环的目的是在有工作时保持线程忙,并在没有工作时让线程进入休眠状态。

运行循环管理不是完全自动的。您仍然必须设计线程的代码以在适当的时间启动运行循环并响应传入的事件。Cocoa和Core Foundation都提供了运行循环对象来帮助您配置和管理线程的运行循环。您的应用程序不需要显式创建这些对象; 每个线程(包括应用程序的主线程)都有一个关联的运行循环对象。但是,只有辅助线程需要显式运行其运行循环。作为应用程序启动过程的一部分,应用程序框架会自动在主线程上设置并运行运行循环。

以下部分提供有关运行循环以及如何为应用程序配置它们的更多信息。有关运行循环对象的其他信息,请参阅NSRunLoop类参考和CFRunLoop参考。

Figure 3-1 shows the conceptual structure of a run loop and a variety of sources. The input sources deliver asynchronous events to the corresponding handlers and cause the runUntilDate: method (called on the thread’s associated NSRunLoop object) to exit. Timer sources deliver events to their handler routines but do not cause the run loop to exit.

图3-1 运行循环的结构及其来源

RunLoop 运行机制原理逻辑与GCD及线程关系剖析_第1张图片
image

回到 macOS/iOS 平台上,对于 event loop 的具体实现有两个:

Foundation 框架中的 NSRunLoop

Core Foundation 框架中的 CFRunLoop

其中 NSRunLoop 是对 CFRunLoop 的简单封装,需要着重研究的只有 CFRunLoop。

2,Run Loop 实现

网上目前有关 Run Loop 的文章, 10 篇里面可能有 8 篇都是重复了 深入理解RunLoop 中的代码。包括本人阅读不下5遍,在阅读runloop源码前后都去阅读YY作者文章,然而这都是经过作者大量简化过的版本,隐藏了大量的细节。

RunLoop 与线程的关系

首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。

苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样:


CFRunLoopGetMain :

CFRunLoopRef CFRunLoopGetMain(void) {

    CHECK_FOR_FORK();

    static CFRunLoopRef __main = NULL; // no retain needed

    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed

    return__main;

}

CFRunLoopRef CFRunLoopGetCurrent(void) {

    CHECK_FOR_FORK();

    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);

    if(rl)returnrl;

    return _CFRunLoopGet0(pthread_self());

}

CFRunLoopGet0

无论是 CFRunLoopGetMain 还是 CFRunLoopGetCurrent ,两者调用了 CFRunLoopGet0 :

// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef

static CFMutableDictionaryRef   __CFRunLoops = NULL;

/// 访问 loopsDic 时的锁

static  CFLock_t  loopsLock = CFLockInit;

// should only be called by Foundation

// t==0 is a synonym for "main thread" that always works

/// 获取一个 pthread 对应的 RunLoop。

CF_EXPORT  CFRunLoopRef  _CFRunLoopGet0(pthread_tt) {

    if(pthread_equal(t,kNilPthreadT)) {

t =pthread_main_thread_np();

    }
    __CFLock(&loopsLock);

    if (!__CFRunLoops) {

         // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。

        __CFUnlock(&loopsLock);

CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);

CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

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

if(!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void*volatile*)&__CFRunLoops)) {

    CFRelease(dict);

}

CFRelease(mainLoop);

        __CFLock(&loopsLock);

    }

    /// 直接从 Dictionary 里获取。

    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

    __CFUnlock(&loopsLock);

    if(!loop) {

        //直接创建一个

CFRunLoopRef newLoop = __CFRunLoopCreate(t);

        __CFLock(&loopsLock);

loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

if(!loop) {

        /// 取不到时,创建一个

    CFDictionarySetValue(__CFRunLoops,pthreadPointer(t), newLoop);

    loop = newLoop;

}

        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it

        __CFUnlock(&loopsLock);

CFRelease(newLoop);

    }

    if (pthread_equal(t, pthread_self())) {

        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);

        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {

               // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。

            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void*)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void(*)(void*))__CFFinalizeRunLoop);

        }

    }

    returnloop;

}

从上面的代码可以看出,
1,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里,线程作为key,RunLoop作为value。
2,线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有,主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建。
3,RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
4,你只能在一个线程的内部获取其 RunLoop(主线程除外)。

CHECK_FOR_FORK()
在两个函数里,都有使用了 CHECK_FOR_FORK() 。
它应该是属于多进程情况下的一个断言。

Threading Programming Guide 中,有这么一段话:

Warning: When launching separate processes using the fork function, you must always follow a call to fork with a call to exec or a similar function. Applications that depend on the Core Foundation, Cocoa, or Core Data frameworks (either explicitly or implicitly) must make a subsequent call to an exec function or those frameworks may behave improperly.

也就是说,当通过 fork 启动一个新进程的时候,你必须要接着调用一个 exec 或类似的函数。而依赖于 Core Founadtion / Cocoa / Core Data 框架的应用,必须调用 exec 函数,否则这些框架也许不能正确的工作。

所以为了保证安全,使用 CHECK_FOR_FORK 进行检查。

FORK

这里简单提一下 fork 。
在 UNIX 中,用 fork 来创建子进程,调用 fork( ) 的进程被称为父进程,新进程是子进程,并且几乎是父进程的完全复制(变量、文件句柄、共享内存消息等相同,但 process id 不同)。

因为子进程和父进程基本是一样的,要想让子进程去执行其他不同的程序,子进程就需要调用 exec ,把自身替换为新的进程,其中process id不变,但原来进程的代码段、堆栈段、数据段被新的内容取代,来执行新的程序。

这样 fork 和 exec 就成为一种组合。

而在 iOS 这样的类 UNIX 系统里,基本上也都要通过 fork 的形式来创建新的进程。

假如没有执行完 exec ,那么执行的代码段等内容,还是父进程里的,出现问题可以说百分之百。这就是 CHECK_FOR_FORK 检查的目的。

RunLoop结构体

通过源码我们找到__CFRunLoop结构体

#pragma mark -
#pragma mark Run Loops

struct _block_item {
    struct _block_item *_next;
    CFTypeRef _mode;    // CFString or CFSet
    void (^_block)(void);
};

typedef struct _per_run_data {
    uint32_t a;
    uint32_t b;
    uint32_t stopped;
    uint32_t ignoreWakeUps;
} _per_run_data;
/*
 typedef struct __CFRuntimeBase {
 uintptr_t _cfisa;
 uint8_t _cfinfo[4];
 #if __LP64__
 uint32_t _rc;
 #endif
 } CFRuntim
 */
/*
 CFRuntimeBase    _base    应该是 Core Foundation 对象都需要的东西
 pthread_mutex_t    _lock    一个 mutex,根据注释,是用来锁对于 mode 的访问的。对其操作由 __CFRunLoopLockInit、__CFRunLoopLock 和 __CFRunLoopUnlock 函数封装
 __CFPort    _wakeUpPort    __CFPort 实际上就是 mach_port_t。根据注释,这是用来唤醒 run loop 的 mach port,被 CFRunLoopWakeUp 函数使用
 Boolean    _unused    和变量名一样,没有使用的变量,猜测是为了对齐用?
 _per_run_data *    _perRunData    每次调用 CFRunLoopRun 或者 CFRunLoopRunInMode 函数(实际就是对 CFRunLoopRunSpecific 函数的调用),也就是每次 run 的一个独立数据。相关操作:__CFRunLoopPushPerRunData 和 __CFRunLoopPopPerRunData,这个又 push 又 pop 的原因是因为 run loop 可以嵌套调用
 pthread_t    _pthread    对应的 pthread
 uint32_t    _winthread    Windows 下对应线程
 CFMutableSetRef    _commonModes    存放 common mode 的集合
 CFMutableSetRef    _commonModeItems    每个 common mode 都有的 item (source, timer and observer) 集合
 CFRunLoopModeRef    _currentMode    当前 run 的 mode
 CFMutableSetRef    _modes    这个 run loop 所有的 mode 集合
 sturct _block_item *    _blocks_head    存放 CFRunLoopPerformBlock 函数添加的 block 的双向链表的头指针
 sturct _block_item *    _blocks_tail    同上尾脂针
 CFAbsoluteTime    _runTime    估计是一共跑着的时间(但实际上根本没使用)
 CFAbsoluteTime    _sleepTime    一共睡了多久
 CFTypeRef    _counterpart    给 Swift 用的玩意
 */
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;

除一些记录性属性外,主要来看一下以下两个成员变量
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
CFRunLoopModeRef 其实是指向__CFRunLoopMode结构体的指针,__CFRunLoopMode结构体源码如下

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
/*
 CFRuntimeBase    _base    应该是 Core Foundation 对象都需要的东西
 pthread_mutex_t    _lock    一个 mutex,锁 mode 里的各种操作。根据注释,需要 run loop 的锁先锁上才能锁这个锁。同样也有两个函数 __CFRunLoopModeLock 和 __CFRunLoopModeUnlock 对其操作进行了简单封装
 CFStringRef    _name    当然是名字啦
 Boolean    _stopped    是否停止了
 char[3]    _padding    可能是为了对齐用的吧……
 CFMutableSetRef    _sources0    Source0 集合,也就是非 port 的 source
 CFMutableSetRef    _sources1    Source1 集合,也就是基于 port 的 source
 CFMutableArrayRef    _observers    Observer 集合
 CFMutableArrayRef    _timers    Timer 集合
 CFMutableDictionaryRef    _portToV1SourceMap    Key 是 port,value 是对应 source1 的字典
 __CFPortSet    _portSet    所有 port 的集合
 CFIndex    _observerMask    需要 observe 的事件的 mask,一个小优化
 dispatch_source_t    _timerSource    用来实现 timer 的 GCD timer
 dispatch_queue_t    _queue    放 _timerSource 的队列
 Boolean    _timerFired    _timerSource 是否被启动
 Boolean    _dispatchTimerArmed    timer 是否被安装上了,或者说是否开启了
 __CFPort    _timerPort    使用 MK timer 时的端口
 Boolean    _mkTimerArmed    timer 是否被开启
 uint64_t    _timerSoftDeadline    下一个计划启动的时间
 uint64_t    _timerHardDeadline    下一个最迟启动的时间(计划加上容忍延迟的时间)
 */
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
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR 
};

RunLoop 对外的接口

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

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

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

RunLoop 运行机制原理逻辑与GCD及线程关系剖析_第2张图片
image

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

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

• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

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

};

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

RunLoop 的内部逻辑

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

RunLoop 运行机制原理逻辑与GCD及线程关系剖析_第3张图片
image

官方文档:

The Run Loop Sequence of Events

Each time you run it, your thread’s run loop processes pending events and generates notifications for any attached observers. The order in which it does this is very specific and is as follows:

Notify observers that the run loop has been entered.

Notify observers that any ready timers are about to fire.

Notify observers that any input sources that are not port based are about to fire.

Fire any non-port-based input sources that are ready to fire.

If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.

Notify observers that the thread is about to sleep.

Put the thread to sleep until one of the following events occurs:

An event arrives for a port-based input source.

A timer fires.

The timeout value set for the run loop expires.

The run loop is explicitly woken up.

Notify observers that the thread just woke up.

Process the pending event.

If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.

If an input source fired, deliver the event.

If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.

Notify observers that the run loop has exited.

/// 用DefaultMode启动
void CFRunLoopRun(void) {

    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);

}
/// 用指定的Mode启动,允许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {

    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);

}

以下是官方源码 并没有删减代码,这样大家更好理解点

SInt32 CFRunLoopRunSpecific(CFRunLoopRefrl,CFStringRefmodeName,CFTimeIntervalseconds,BooleanreturnAfterSourceHandled) {    /* DOES CALLOUT */

    CHECK_FOR_FORK();

     //检查 run loop 是否正在销毁

    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;

    __CFRunLoopLock(rl);

    // 查找 modeName 指定的 mode

    CFRunLoopModeRefcurrentMode =__CFRunLoopFindMode(rl, modeName,false);

    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {// 没有找到 mode 或者 mode 里面没有任何事件源的话,返回 kCFRunLoopRunFinished

        //这里比较奇怪的是 Boolean did = false 直接写死了 did 的值,后面又是 return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished . 怀疑 did 的值,应该还有一段代码是决定kCFRunLoopRunHandledSource的结果,被苹果隐藏了没有开源出来

Booleandid =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;

if(currentMode->_observerMask&kCFRunLoopEntry)__CFRunLoopDoObservers(rl, currentMode,kCFRunLoopEntry);

result =__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

if(currentMode->_observerMask&kCFRunLoopExit)__CFRunLoopDoObservers(rl, currentMode,kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);

        __CFRunLoopPopPerRunData(rl, previousPerRun);

rl->_currentMode= previousMode;

    __CFRunLoopUnlock(rl);

    returnresult;

}
/**

 *  运行run loop

 *

 *  @param rl              运行的RunLoop对象

 *  @param rlm            运行的mode

 *  @param seconds        run loop超时时间

 *  @param stopAfterHandle true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止

 *  @param previousMode    上一次运行的mode

 *

 *  @return 返回4种状态

 */

/* rl, rlm are locked on entrance and exit */

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    //mach_absolute_time is a CPU/Bus dependent function that returns a value based on the number of "ticks" since the system started up.

    // mach_absolute_time  是一个CPU/总线依赖函数,返回一个基于系统启动后的时钟"嘀嗒"数。在macOS上可以确保它的行为,并且,它包含系统时钟所包含的所有时间区域。其可获取纳秒级的精度

    //获取系统启动之后cpu嘀嗒数

    uint64_t startTSR =mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {

        __CFRunLoopUnsetStopped(rl);

        return kCFRunLoopRunStopped;

    }elseif(rlm->_stopped) {

        rlm->_stopped =false;

        return kCFRunLoopRunStopped;

    }

    /*

     kern_return_t mach_port_names 

     (ipc_space_t                                task,

     mach_port_name_array_t                  * names,

     mach_msg_type_number_t                * namesCnt,

     mach_port_type_array_                    * types,

     mach_msg_type_number_t                * typesCnt );

     mach_port_name_t must be an unsigned type.  Port values

     *  have two parts, a generation number and an index.

     *  These macros encapsulate all knowledge of how

     *  a mach_port_name_t is laid out.  They are made visible 

     *  to user tasks so that packages to map from a mach_port_name_t

     *  to associated user data can discount the generation

     *  nuber (if desired) in doing the mapping.

     *

     *  Within the kernel, ipc/ipc_entry.c implicitly assumes

     *  when it uses the splay tree functions that the generation

     *  number is in the low bits, so that names are ordered first

     *  by index and then by generation.  If the size of generation

     *  numbers changes, be sure to update IE_BITS_GEN_MASK and

     *  friends in ipc/ipc_entry.h.

     返回有关任务端口名称空间的信息。

     该mach_port_names返回有关任务的端口名称空间。它返回任务的当前活动名称,表示某些端口,端口集或死命名。对于每个名称,它还返回任何类型的权限 任务(由mach_port_type返回的相同信息)。

     请注意,当对mach_port_names的调用返回时,两个输出数组(名称和类型)中的条目数相等(namesCnt等于typesCnt)。此接口返回两个单独计数的事实是Mach Interface Generator的工件。

     task

     查询端口名称空间的任务。

     names

     [指向动态数组mach_port_name_t的指针]任务端口名称空间中的端口,端口集和死名称的名称,没有特定的顺序。

     namesCnt

     [out scalar]返回的名称数量。

     types

     [指向动态数组mach_port_type_t的指针]每个相应名称的类型。指示任务使用该名称保留的权限类型。

     typesCnt

     [out scalar]返回的类型数。

     返回值

     仅适用一般性错误。

     */

    //mach 端口, 线程之间通信的对象  mach端口,内核进程通信消息端口。初始为0

    mach_port_name_t dispatchPort =MACH_PORT_NULL;

    //pthread_main_np() 获取主线程 这里主要是为了判断当前线程是否为主线程 

    // 检测是否在主线程 && ( (是队列发的消息&&mode为null)||(不是队列发的消息&&不在主队列))

    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &&NULL== previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &&0== _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));

    /*

     需要在主线程,run loop 也是主线程的 run loop,并且 mode 是 common mode

     从 GCD 的私有 API 获取端口(4CF 表示 for Core Foundation)

     则给 dispatchPort 赋值为主线程收发消息的端口

     */

    //如果是队列安全的,并且是主线程runloop,设置它对应的通信端口

    /*

     _dispatch_get_main_queue_port_4CF是 dispatch_get_main_queue_handle_4CF 的宏,存在 libdispatch 中,里面对它的实现为:

     dispatch_runloop_handle_t

     _dispatch_get_main_queue_handle_4CF(void)

     {

     dispatch_queue_t dq = &_dispatch_main_q;

     dispatch_once_f(&_dispatch_main_q_handle_pred, dq,

     _dispatch_runloop_queue_handle_init);

     return _dispatch_runloop_queue_get_handle(dq);

     }

     返回的是主线程 runloop 所关联的的端口。

     */

    if(libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort =_dispatch_get_main_queue_port_4CF();

    /*

     USE_DISPATCH_SOURCE_FOR_TIMERS 这个宏的值为 1,也就是说有使用 GCD 来实现 timer,当然 USE_MK_TIMER_TOO 这个宏的值也是 1,表示也使用了更底层的 timer。

     */

    //如果使用 GCD timer 作为 timer 的实现的话,进行准备工作

#if USE_DISPATCH_SOURCE_FOR_TIMERS

    //  MACOSX 下,声明一个 mode 的队列通信端口(在 MACOSX 环境中):

    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

    //使用GCD实现runloop超时功能 

    //GCD管理的定时器,用于实现runloop超时机制

    dispatch_source_ttimeout_timer =NULL;

    //创建上下文 跟GCD 定时器关联一起

    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;

    }elseif(seconds <= TIMER_INTERVAL_LIMIT) {// seconds为超时时间,超时时执行__CFRunLoopTimeout函数 

        //根据是否为主线程,设置队列是主队列还是后台队列

        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;

        //runloop

        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);

        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);

        /*

         void dispatch_set_context(dispatch_object_t object, void *context);

         将应用程序定义的上下文与对象相关联。

         您的应用程序可以将自定义上下文数据与对象相关联,仅供您的应用程序使用。您的应用程序必须根据需要分配和取消分配数据。

         object

         这个参数不能NULL。

         context

         对象的新应用程序定义的上下文。这可以NULL。

         void dispatch_source_set_event_handler_f(dispatch_source_t source, dispatch_function_t handler);

         为给定的调度源设置事件处理函数。

         source

         调度源要修改。这个参数不能NULL。

         handler

         事件处理函数提交到源的目标队列。传递给事件处理函数的context参数是处理程序调用时调度源的当前上下文。这个参数不能NULL。

         */

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

        //恢复唤起 timer 执行

        dispatch_resume(timeout_timer);

    }else { // infinite timeout // 永不超时

        //无限期超时

        seconds =9999999999.0;

        timeout_context->termTSR =UINT64_MAX;

    }

    //设置超时timer 结束  标志位默认为true

    // 设置判断是否为最后一次 dispatch 的端口通信的变量

    BooleandidDispatchPortLastTime =true;

    //记录最后runloop状态,用于return 退出 runloop

    // 设置一个结果变量,最后为几个 CFRunLoopRunInMode 里返回状态之一。

    int32_t retVal =0;

    do{

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

        // 一个状态变量,用于 消息状态 标志,初始值为 UNCHAMGED

        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;

        voucher_t voucherCopy =NULL;

#endif

        // 初始化一个存放内核消息的缓冲池

        uint8_t msg_buffer[3*1024];

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

        //声明和 mach port 有关的 port 和 msg 变量

        mach_msg_header_t *msg =NULL;

        // 活动端口,本地用来接收消息的端口

        mach_port_t livePort = MACH_PORT_NULL;

#endif

        // 取所有需要监听的port,runloopMode

        // 声明一个类型为 CFPortSet 的 waitSet, 值为 run loop mode 里的 portSet.

        __CFPortSet waitSet = rlm->_portSet;

        // 设置RunLoop为可以被唤醒状态

        //将 run loop 从忽略唤醒消息的状态 unset ,开始接受唤醒消息

        __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) 回调。

        // 执行 Source0 (非 mach port) 。 有事件处理返回 true,没有事件返回 false

        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        if(sourceHandledThisLoop) {

            // 执行加入当前 runloop 的 block

            __CFRunLoopDoBlocks(rl, rlm);

        }

        // 如果没有 Sources0 事件处理 并且 没有超时,poll 为 false

        // 如果有 Sources0 事件处理 或者 超时,poll 都为 true

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        //5\. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息

        // 第一次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\. 接收 dispatchPort 端口的消息,dispatch 到 main queue 的事件。

            if(__CFRunLoopServiceMachPort(dispatchPort, &msg,sizeof(msg_buffer), &livePort,0, &voucherState,NULL)) {

                // 如果接收到了消息的话,前往 handle_msg 开始处理 msg

                gotohandle_msg;

            }

#endif

        }

        didDispatchPortLastTime =false;

        //6, 通知 Observers: RunLoop 的线程即将进入休眠(sleep)

        // 注意到如果实际处理了 source0 或者超时,不会进入睡眠,所以不会通知。

        if(!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        //设置标志位,正在睡眠(实际上没有开始睡)

        // 设置RunLoop为休眠状态

        // 设置标志位, Run Loop 休眠

        __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.

        //使用 GCD 的话,将 GCD 端口加入所有监听端口集合中

        __CFPortSetInsert(dispatchPort, waitSet);

        __CFRunLoopModeUnlock(rlm);

        __CFRunLoopUnlock(rl);

        //使用 GCD 的话,将 GCD 端口加入所有监听端口集合中

        // 休眠开始的时间,根据 poll 状态决定为 0 或者当前的绝对时间

        CFAbsoluteTime sleepStart = poll ?0.0: CFAbsoluteTimeGetCurrent();

        // 这里有个内循环,用于接收等待端口的消息

        // 进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop

        //// 等待被唤醒,可以被 sources 1、timers、CFRunLoopWakeUp 函数和 GCD 事件(如果在主线程)

        //7.通过 CFRunLoopServiceMachPort 调用 mach_msg 休眠,等待被 mach_msg 消息唤醒

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

        // 如果在 MACOSX 中

#if USE_DISPATCH_SOURCE_FOR_TIMERS

        // 处理 GCD timer 

        do{

            // 使用 GCD timer 作为 timer 实现的情况

            if (kCFUseCollectableAllocator) {//假如有kCFUseCollectableAllocator分配器,使用 memset 清空msg_buffer

                // objc_clear_stack(0);

                // 

                // 清空 msg_buffer

                memset(msg_buffer,0,sizeof(msg_buffer));

            }

            msg = (mach_msg_header_t *)msg_buffer;

            // 这个函数会睡眠线程 waitSet: 监听端口集合  livePort: 返回收到消息的端口 poll: 根据状态睡眠或者不睡

            // 接收waitSet端口的消息

            // 设置 mach port 通信,会睡眠线程

            __CFRunLoopServiceMachPort(waitSet, &msg,sizeof(msg_buffer), &livePort, poll ?0: TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            // 如果是 timer 端口唤醒的,进行一下善后处理,之后再处理 timer

            // 收到消息之后,livePort 的值为本地接收消息的活动端口

            // modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);

            //modeQueuePort:如果使用 GCD timer 作为 timer 的实现的话,进行准备工作

            //livePort 作为本地接受端口

            // modeQueue 存在,而且为 livePort

            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.

                //执行 run loop mode 里的队列,直到队列都执行完成

                while(_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));

                if (rlm->_timerFired) {//timer 唤醒的//假如 _timerFired 为真,把 livePort 作为队列端口,在之前服务于 timers

                    // Leave livePort as the queue port, and service timers below

                    rlm->_timerFired =false;

                    break;

                }else {// _timerFired 为假, 并且 msg 存在不为 msg_buffer, 释放 msg

                    if(msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);

                }

            }else{

                // Go ahead and leave the inner loop.

                // 不是 timer 端口唤醒的,进行接下来的处理

                break;

            }

        }while(1);

#else// 不在 MACOSX 中

        // 不使用 GCD timer 作为 timer 实现的情况

        if (kCFUseCollectableAllocator) {//如果 kCFUseCollectableAllocator 分配器,使用 memset 清空 msg_buffer

            // objc_clear_stack(0);

            // 

            memset(msg_buffer,0,sizeof(msg_buffer));

        }

        /// . 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。

        /// • 一个基于 port 的Source 的事件。

        /// • 一个 Timer 到时间了

        /// • RunLoop 自身的超时时间到了

        /// • 被其他什么调用者手动唤醒

        msg = (mach_msg_header_t *)msg_buffer;

        //CFRunLoopServiceMachPort 会让线程休眠

        __CFRunLoopServiceMachPort(waitSet, &msg,sizeof(msg_buffer), &livePort, poll ?0: TIMEOUT_INFINITY, &voucherState, &voucherCopy);

#endif

        //上锁

        __CFRunLoopLock(rl);

        __CFRunLoopModeLock(rlm);

        // 增加记录的睡眠时间

        // 根据 poll 的值,记录休眠时间,休眠时间差

        rl->_sleepTime += (poll ?0.0: (CFAbsoluteTimeGetCurrent() - sleepStart));

        // 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.

        // 将 GCD 端口移除

        //对 waitSet 里的 dispatchPort 端口做移除

        __CFPortSetRemove(dispatchPort, waitSet);

        // 设置 runloop 不可被唤醒

        //让 Run Loop 忽略唤醒消息,因为已经重新在运行了

        __CFRunLoopSetIgnoreWakeUps(rl);

        //  取消runloop的休眠状态

        // user callouts now OK again

        __CFRunLoopUnsetSleeping(rl);

        // 8 通知 observers: kCFRunLoopAfterWaiting, 即停止等待(被唤醒)

        // 注意实际处理过 source 0 或者已经超时的话,不会通知(因为没有睡)

        if(!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

        // 11\. 被什么唤醒就处理什么: 处理通过端口收到的消息

    handle_msg:;

        // 设置 runloop 不可被唤醒

        //将 Run Loop 重新忽略唤醒消息,因为已经重新在运行了

        __CFRunLoopSetIgnoreWakeUps(rl);

        if (MACH_PORT_NULL == livePort) {// 不知道哪个端口唤醒的(或者根本没睡),啥也不干  livePort 为空,什么事都不做

            CFRUNLOOP_WAKEUP_FOR_NOTHING();

            // handle nothing

        }else if (livePort == rl->_wakeUpPort) {// 被 CFRunLoopWakeUp 函数弄醒的,啥也不干 跳回2重新循环 // livePort 等于 run loop 的 _wakeUpPort

            // 被 CFRunLoopWakeUp 函数唤醒的

            CFRUNLOOP_WAKEUP_FOR_WAKEUP();

            // do nothing on Mac OS

        }

#if USE_DISPATCH_SOURCE_FOR_TIMERS// 在 MACOSX 里

        elseif(modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {//被 timers 唤醒,处理 timers 

            //livePort 等于 modeQueuePort

            //9.1-1 被 timers 唤醒,处理 timers

            CFRUNLOOP_WAKEUP_FOR_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

        elseif(rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {// 被 模mk timers 唤醒,处理 timers

            //livePort 等于 run loop mode 的 _timerPort

            // 9.1-2 被 timers 唤醒,处理 timers

            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

        else if (livePort == dispatchPort) {//9.2 如果有dispatch到main_queue的block,执行block。

            // 被 GCD 唤醒或者从第 7 步跳转过来的话,处理 GCD

            CFRUNLOOP_WAKEUP_FOR_DISPATCH();

            __CFRunLoopModeUnlock(rlm);

            __CFRunLoopUnlock(rl);

            //设置 CFTSDKeyIsInGCDMainQ 位置的 TSD 为 6 .

            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void*)6,NULL);

            // 执行block

            // 处理 msg

            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

            //设置 CFTSDKeyIsInGCDMainQ 位置的 TSD 为 0.

            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void*)0,NULL);

            __CFRunLoopLock(rl);

            __CFRunLoopModeLock(rlm);

            //设置变量

            sourceHandledThisLoop =true;

            didDispatchPortLastTime =true;

        }else{

            // source1 事件 

            //被 source (基于 mach port) 唤醒

            CFRUNLOOP_WAKEUP_FOR_SOURCE();

            // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.

            // 假如我们 从这个 mach_msg 中接收到一个 voucher,然后在 TSD 中放置一个复制的新的 voucher.

            // CFMachPortBoost 会在 TSD 中去查找这个 voucher. 

            // 通过使用 TSD 中的值,我们将 CFMachPortBoost 绑定到这个接收到的 mach_msg 中,在这两段代码之间没有任何机会再次设置凭证

            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void*)voucherCopy, os_release);

            // 被 sources 1 唤醒,处理 sources 1

            //9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件

            // Despite the name, this works for windows handles as well

            // 根据接收消息的 port 寻找 source1 事件

            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);

            if (rls) {// 有 source1 事件待处理  //如果 rls 存在

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

                mach_msg_header_t *reply =NULL;

                // 处理 source1 事件  //处理 Source ,并返回执行结果

                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

                if(NULL!= reply) {//发送reply消息(假如 reply 不为空)

                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size,0, MACH_PORT_NULL,0, MACH_PORT_NULL);

                    //释放 reply 变量

                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);

                }

#endif

            }

            // Restore the previous voucher

            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);

        } 

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

        if(msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);

#endif

        //12\. 再一次处理 blocks

        __CFRunLoopDoBlocks(rl, rlm);

        // 13\. 判断是否退出,不需要退出则跳转回第 2 步  //根据一次循环后的状态,给 retVal 赋值 。状态不变则继续循环

        if(sourceHandledThisLoop && stopAfterHandle) {

            retVal = kCFRunLoopRunHandledSource;

        }elseif(timeout_context->termTSR < mach_absolute_time()) {

            retVal = kCFRunLoopRunTimedOut;

        }elseif(__CFRunLoopIsStopped(rl)) {

            __CFRunLoopUnsetStopped(rl);

            retVal = kCFRunLoopRunStopped;

        }elseif(rlm->_stopped) {

            rlm->_stopped =false;

            retVal = kCFRunLoopRunStopped;

        }elseif(__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {

            retVal = kCFRunLoopRunFinished;

        }

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI

        // 循环一次后收尾处理

        voucher_mach_msg_revert(voucherState);

        os_release(voucherCopy);

#endif

    }while(0== retVal);

    if(timeout_timer) {//如果存在,取消并释放

        dispatch_source_cancel(timeout_timer);

        dispatch_release(timeout_timer);

    }else {//不存在,将对应的 timeour_context 释放

        free(timeout_context);

    }

    //结束返回 retVal 状态。

    return retVal;

}

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

对__CFRunLoopRun方法细聊

1,开始获取当前时间点
2, __CFRunLoopIsStopped判断是否已经停止 停止就返回停止状态出去
3 声明一个 mach_port 通信的端口dispatchPort ,如果是主线程就将主线程端口赋值给dispatchPort,dispatchPort = _dispatch_get_main_queue_port_4CF();
4,MACOSX 下,声明一个 mode 的队列通信端口(在 MACOSX 环境中):modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
5,用GCD管理runloop超时机制
定时器开始调用事件__CFRunLoopTimeout

static void __CFRunLoopTimeout(void *arg) {
    struct __timeout_context *context = (struct __timeout_context *)arg;
    context->termTSR = 0ULL;
    CFRUNLOOP_WAKEUP_FOR_TIMEOUT();
    //等待runloop消息
    CFRunLoopWakeUp(context->rl);
    // The interval is DISPATCH_TIME_FOREVER, so this won't fire again
}

6,进入do while循环:
1),声明状态变量用于消息状态标志

 voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;

2),初始化一个存放内核消息缓存池

uint8_t msg_buffer[3 * 1024];

3),声明和 mach port 有关的 port 和 msg 变量

mach_msg_header_t *msg = NULL;

活动端口,本地用来接收消息的端口

 mach_port_t livePort = MACH_PORT_NULL;

4),取所有需要监听的port,runloopMode

__CFPortSet waitSet = rlm->_portSet;

二,通知 Observers: RunLoop 即将触发 Timer 回调

if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

三 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。

if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

__CFRunLoopDoObservers里面调用方法调用系统方法执行回调出去

 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);

5),执行被加入的block

__CFRunLoopDoBlocks(rl, rlm);

block执行实际上是调用方法执行block的

  __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

四,RunLoop 触发 Source0 (非port) 回调。

 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            // 执行加入当前 runloop 的 block
           //执行完 source0 后,假如还有需要执行的,再执行一次 block
            __CFRunLoopDoBlocks(rl, rlm);
    }

__CFRunLoopDoSources0代码实际调用系统方法

 if (__CFIsValid(rls)) {
                __CFRunLoopSourceUnlock(rls);
                    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
                CHECK_FOR_FORK();
                sourceHandled = true;
            } else {
                __CFRunLoopSourceUnlock(rls);
            }

五,如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息

// 从缓冲区读取消息
            msg = (mach_msg_header_t *)msg_buffer;
              // 5. 接收 dispatchPort 端口的消息,dispatch 到 main queue 的事件。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                 // 如果接收到了消息的话,前往 handle_msg 开始处理 msg
                goto handle_msg;
            }

对于__CFRunLoopServiceMachPort方法 详细介绍一下,后面会还有多个地方用到的

/**
 *  接收指定内核端口的消息
 *
 *  @param port        接收消息的端口
 *  @param buffer      消息缓冲区
 *  @param buffer_size 消息缓冲区大小
 *  @param livePort    暂且理解为活动的端口,接收消息成功时候值为msg->msgh_local_port,超时时为MACH_PORT_NULL
 *  @param timeout     超时时间,单位是ms,如果超时,则RunLoop进入休眠状态
 *
 *  @return 接收消息成功时返回true 其他情况返回false
 */
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;// 消息头的标志位
        msg->msgh_local_port = port;// 本地消息端口
        msg->msgh_remote_port = MACH_PORT_NULL;// 目标消息端口
        msg->msgh_size = buffer_size;// 消息缓冲区大小
        msg->msgh_id = 0;// msg id
        // 根据 timeout 的值,决定 Run Loop 是休眠还是执行
        // timeout  为 TIMEOUT_INFINITY 时,才执行 CFRUNLOOP_SLEEP() 休眠
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
         // 系统底层处理消息的机制
        // 发送并接收 mach port 消息
        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        /*!
          extern void voucher_mach_msg_revert(voucher_mach_msg_state_t state);
         * @function voucher_mach_msg_revert
         *
         * @abstract
         * Restore thread voucher state previously modified by voucher_mach_msg_adopt().
         *
         * @discussion
         * Current thread voucher reference is released.
         * No change to thread voucher state if passed VOUCHER_MACH_MSG_STATE_UNCHANGED.
         *
         * @param state
         * The thread voucher state to restore.
         */
       
        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        // 在 mach_msg 之后注意所有 voucher 相关的正常运行
        // 假如我们没有释放前面的 voucher , 将会出现内存泄漏
        voucher_mach_msg_revert(*voucherState);
        
        /*!
         extern voucher_mach_msg_state_t voucher_mach_msg_adopt(mach_msg_header_t *msg);
         * @function voucher_mach_msg_adopt
         *
         * @abstract
         * Adopt the voucher contained in the specified message on the current thread
         * and return the previous thread voucher state.
         有关线程的信息
         *
         * @discussion
         * Ownership of the mach voucher in the message is transferred to the current
         * thread and the message header voucher fields are cleared.
         *
         * @param msg
         * The message to query and modify.
         *
         * @result
         * The previous thread voucher state or VOUCHER_MACH_MSG_STATE_UNCHANGED if no
         * state change occurred.
         
         */
        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
          // 会有调用者去负责调用 voucher_mach_msg_revert .它会让接收到的 voucher 变成当前的这一个值。
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                // 调用者 在这里 请求了一个 voucher 的复制的值。通过在 mach_msg 前后做这个操作,我们确保在 mach_msg 返回和使用 voucher 的复制值的时候,没有涉及设置 voucher 的值。
                // 为确保  CFMachPortBoost 使用的  voucher ,所以我们只在 voucher 不是  state_unchanged 的时候,去设置 TSD 。
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        /*
         void CFRunLoopWakeUp(CFRunLoopRef rl);
         Wakes a waiting CFRunLoop object.
         A run loop goes to sleep when it is waiting for a source or timer to become ready to fire. If no source or timer fires, the run loop stays there until it times out or is explicitly woken up. If a run loop is modified, such as a new source added, you need to wake up the run loop to allow it to process the change. Version 0 sources use CFRunLoopWakeUp to cause the run loop to wake up after setting a source to be signaled, if they want the source handled immediately.
         运行循环在等待源或计时器准备好触发时进入休眠状态。如果没有源或计时器触发,则运行循环将保持不变,直到超时或显式唤醒为止。如果修改了运行循环,例如添加了新的源,则需要唤醒运行循环以允许它处理更改。版本0源用于在设置要发信号的源之后唤醒运行循环,如果他们想要立即处理源。CFRunLoopWakeUp
         rl
         The run loop to wake up.
         */
         // 唤醒 Run Loop
        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) { // 接收/发送消息成功,给 livePort 赋值为 msgh_local_port
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {// 接收消息超时
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
          // 消息体过大,分配更多的内存
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

可以看出里面调用系统mach_msg方法,mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:


RunLoop 运行机制原理逻辑与GCD及线程关系剖析_第4张图片
image.png

六,通知 Observers: RunLoop 的线程即将进入休眠(sleep)

     // 注意到如果实际处理了 source0 或者超时,不会进入睡眠,所以不会通知。
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        //设置标志位,正在睡眠(实际上没有开始睡)
       // 设置RunLoop为休眠状态
        // 设置标志位, Run Loop 休眠
    __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.

        //使用 GCD 的话,将 GCD 端口加入所有监听端口集合中
        __CFPortSetInsert(dispatchPort, waitSet);

6)记录开始休眠时间

  //使用 GCD 的话,将 GCD 端口加入所有监听端口集合中
        // 休眠开始的时间,根据 poll 状态决定为 0 或者当前的绝对时间
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

七,通过 CFRunLoopServiceMachPort 调用 mach_msg 休眠,等待被 mach_msg 消息唤醒

// 不使用 GCD timer 作为 timer 实现的情况
        if (kCFUseCollectableAllocator) {//如果 kCFUseCollectableAllocator 分配器,使用 memset 清空 msg_buffer
            // objc_clear_stack(0);
            // 
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        /// . 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
        /// • 一个基于 port 的Source 的事件。
        /// • 一个 Timer 到时间了
        /// • RunLoop 自身的超时时间到了
        /// • 被其他什么调用者手动唤醒
        msg = (mach_msg_header_t *)msg_buffer;
        //CFRunLoopServiceMachPort 会让线程休眠
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

由于字数有限制 没有写下一下 下一篇续https://www.jianshu.com/p/3bcf780d55c3

你可能感兴趣的:(RunLoop 运行机制原理逻辑与GCD及线程关系剖析)