RunLoop 源码阅读

前言

这一篇文章主要在于 Run Loop 源码的阅读,内容有点长,须要一些基础。html

Event Loop

Run Loop 是一个 iOS 开发里的基础概念,它并不是独有的机制,不少系统和框架都有相似的实现,Run Loop 是 Event Loop (事件循环)机制的在 iOS 平台的一种实现。 查阅 wikipedia 有关 Event Loop 的描述:前端

在计算机科学里, Event Loop / Run Loop 是一个用于等待和发送消息/事件的程序结构,在程序中等待和派发一系列事件或者消息。它经过向“事件提供者”发出请求来工做,一般会阻塞请求,直到事件到达,而后调用相应的事件处理程序。linux

Event Driven

说到 Event Loop ,其实还应该了解到 Event-Driven (事件驱动)。git

Event-Driven 的出现,在于解决图形界面和用户交互的问题:github

一般 GUI 程序的交互事件执行是由用户来控制的,没法预测它发生的节点,对应这样的状况,须要采用 Event-Driven 的编程方法。编程

Event-Driven 的实现原理,基本就是使用 Event Loop 完成。Event-Driven 程序的执行,能够归纳成:windows

启动 ——> 事件循环(即等待事件发生并处理之)。数组

在 GUI 的设计场景下,通常写代码会是下面的思惟:安全

用户输入 -> 事件响应 -> 代码运行 -> 刷新页面状态bash

Event

咱们一直在说 Event Loop 和 Event-Driven 。那什么是 Event (事件) 呢?

在 Event-Driven 中,能够把一切行为都抽象为 Event 。例如: IO 操做完成,用户点击按钮,一个图片加载完成,文本框的文字改变等等状况,均可以看做是一个 Event 。

Event Handler

当 Event 被放到 Event Loop 里进行处理的时候,会调用预先注册过的代码对 Event 作处理。这就叫 Event Handler 。

Event Handler 其实就是对 Event 的响应,能够叫作事件回调,事件处理,事件响应,都是同样的概念。

这里须要注意的是,一个 Event 并不必定有 Event Handler .

Event Loop 解决了什么问题

通常来讲,操做分为同步和异步。

同步操做,是一个接一个的处理。等前一个处理完,再执行下一个。那么在一些耗时任务上,好比有不少 I/O 操做 或者 网络请求 的任务,线程就会有长时间在等待任务结果。

异步操做,是不用等待执行结果的,能够直接在这期间执行另外的任务。等到任务结果出来以后,再进行处理。

实际上 Event Loop 就是实现异步的一种方法。

对于有回调的 Event,线程不用一直等待任务的结果出来再去执行下一个。而是等 Event 被加入到 Event Loop 时,再去执行。若是一个 Event 也没有,那线程就会休眠,避免浪费资源。

若是没有 Event Loop 来实现异步操做,那咱们的程序会很容易出现卡顿。

扩展 : JavaScript 在单线程条件下运行,能够完成异步操做,也是基于 Event Loop 机制。 建议能够参考 JavaScript异步编程 的内容来理解,更以帮助咱们举一反三,学习到通用的知识。

Run Loop 实现

网上目前有关 Run Loop 的文章, 10 篇里面可能有 8 篇都是重复了 深刻理解RunLoop 中的代码。

然而这都是通过做者大量简化过的版本,隐藏了大量的细节。

其实从细节里面,咱们同样能够学习到不少东西,不妨尝试去阅读一下。

咱们知道 CFRunLoopRef 的代码是开源的,能够查看源代码来看它的实现,我选择的版本是 CF-1153.18 中的 CFRunLoop.c 。

获取 Run Loop

因为苹果不容许咱们直接建立 RunLoop,只提供 2 个获取操做的函数:

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

复制代码
  • CFRunLoopGetCurrent :
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}


复制代码

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 检查的目的。

Thread-specific data

为了帮助理解,还须要先说 Thread-specific data (TSD),它能够叫做 线程私有数据 , 这个概念来自于 unix 之中。

它是存储和查询与某个线程相关数据的一种机制:

进程内的全部线程,共享进程的数据空间,所以全局变量为全部线程所共有。 而有时线程也须要保存本身的私有数据,这时能够建立线程私有数据(Thread-specific Data)TSD来解决。 在线程内部,私有数据能够被各个函数访问,但对其余线程是屏蔽的。例如咱们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每一个函数都应该能够调用它;但它又不能是一个全局变量。

Pthreads 里,把它叫作 Thread-local storage (线程私有存储) , 有如下几个相关的操做函数:

- pthread_key_create(): 分配用于标识进程中线程特定数据的pthread_key_t类型的键
- pthread_key_delete(): 销毁现有线程特定数据键
- pthread_setspecific(): 为指定线程的特定数据键设置绑定的值
- pthread_getspecific(): 获取调用线程的键绑定值,并将该绑定存储在 value 指向的位置中
复制代码

在苹果的平台上,基本就是利用上面操做实现 TSD 。

RunLoop 实际上就属于 TSD 的里存储的一种数据。因此咱们讲, RunLoop 和线程是一一对应的。而 RunLoop 会在线程销毁时,跟着一块儿清理,也是因为线程私有数据的机制。

TSD 对应存储的 key 有相关的析构函数,线程退出时,析构函数函数就会按照操做系统,实现定义的顺序被调用。因此在 CFSetTSD 会有一个析构函数的参数位置。

CFGetTSD / CFSetTSD

关于 CFGetTSD / CFSetTSD , 在 ForFoundationOnly.h 找到定义:

// ---- Thread-specific data --------------------------------------------

// Get some thread specific data from a pre-assigned slot.
CF_EXPORT void *_CFGetTSD(uint32_t slot);

// Set some thread specific data in a pre-assigned slot. Don't pick a random value. Make sure you're using a slot that is unique. Pass in a destructor to free this data, or NULL if none is needed. Unlike pthread TSD, the destructor is per-thread.
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, void (*destructor)(void *));
复制代码

TSD 也就是 thread specific data 的缩写了。

按照注释,说明 CFGetTSD 的做用是 -- 从预先赋值的位置,获得 TSD

上面也说明了 CFSetTSD 的做用 -- 在预先位置设置 TSD 。 这个数据不能够是随机的值,并保证你使用的位置有惟一性。若是须要释放这个数据,就传入析构函数;若是不须要释放,则传入NULL。和 pthread TSD 不一样的是,这一个析构函数是每个线程都有的。

在上面的CFRunLoopGetCurrent里,是这么使用 _CFGetTSD 的:

CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
复制代码

这里的 slot 值,通常对应的,都应该是相似 __CFTSDKeyRunLoop 的枚举类型的关键字。

CFTSDTable

CFPlatform.c 找到 CFGetTSD / CFSetTSD 具体的实现,发现二者其实都依靠了 CFTSDTable 类型的一个 table 实现。

CFTSDTable 是一个保存 TSD 数据的结构体:

// Data structure to hold TSD data, cleanup functions for each
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;

复制代码

它拥有两个数组: data 存储私有数据, destructors 存储释放函数 . 还有一个 destructorCount ,它顾名思义就是 destructors 数组的数量。

CFGetTSD 主要是取了 table ,获取 table 的 data 数组,按 slot 索引取值。

CFSetTSD 的做用,就是根据 CFTSDTable 的结构,分别是往 table 里设置 data 数组 slot 位置的值,以及 destructors 数组 slot 位置的值:

void *oldVal = (void *)table->data[slot];
    
    table->data[slot] = (uintptr_t)newVal;
    table->destructors[slot] = destructor;

复制代码

CFRunLoopGet0

不管是 CFRunLoopGetMain 仍是 CFRunLoopGetCurrent ,二者调用了 CFRunLoopGet0 :

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {// kNilPthreadT 是一个静态变量为 0
	   t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __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);
    }
    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)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

复制代码

这里对主要的流程作一下解释:

  1. 第一次进入,不管 t 为主线程或者子线程。由于 __CFRunLoops 为 null ,因此会建立一个 mainLoop.
  2. 根据传递进来的 t ,建立对应的 loop 。t 做为 key,loop 做为 value ,存储到 __CFRunLoops 里。若是已经有了对应 loop 存在,则不建立。
  3. 判断 t 是否为当前线程。若是是当前线程,就会利用 CFSetTSD 在 CFTSDKeyRunLoop/CFTSDKeyRunLoopCntr 的位置作设置。

注意:

在了解完 CFSetTSD 的做用, CFTSDKeyRunLoop 设置的意思就很清楚: 在 CFTSDKeyRunLoop 位置,存储 loop , 但不对 loop 设置析构函数。

直接对于 loop 的设置,其实这里已经完成了。

网络文章大部分,直接就说在 CFTSDKeyRunLoopCntr 设置了清理 loop 的回调。

对于为何能够释放 loop ,却避而不谈。

你们想过没有 :

在 CFTSDKeyRunLoopCntr 位置,给出的参数是 PTHREAD_DESTRUCTOR_ITERATIONS - 1 PTHREAD_DESTRUCTOR_ITERATIONS 表示的,是线程退出时,操做系统实现试图销毁线程私有数据的最大次数。

试图销毁次数,和 CFFinalizeRunLoop 这个析构函数,是怎么关联起来的?又是怎么被调用的?

在前面,说过了 CFTSDTable ,实际上在 CFTSDGetTable() 里面,就作了相关 TSD 的设置:

// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
   ...
        pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
   ...
}

复制代码

经过 CF_TSD_KEY ,指定了对应的析构函数 CFTSDFinalize 。

而 CFTSDFinalize 的代码以下:

static void __CFTSDFinalize(void *arg) {
   // Set our TSD so we're called again by pthreads. It will call the destructor PTHREAD_DESTRUCTOR_ITERATIONS times as long as a value is set in the thread specific data. We handle each case below.
    __CFTSDSetSpecific(arg);

    if (!arg || arg == CF_TSD_BAD_PTR) {
        // We've already been destroyed. The call above set the bad pointer again. Now we just return.
        return;
    }
    // On first calls invoke destructor. Later we destroy the data.
    // Note that invocation of the destructor may cause a value to be set again in the per-thread data slots. The destructor count and destructors are preserved. 
    // This logic is basically the same as what pthreads does. We just skip the 'created' flag.
    for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
        if (table->data[i] && table->destructors[i]) {
            uintptr_t old = table->data[i];
            table->data[i] = (uintptr_t)NULL;
            table->destructors[i]((void *)(old));
        }
    }
    
    if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) {    // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
        free(table);
        
        // Now if the destructor is called again we will take the shortcut at the beginning of this function.
        __CFTSDSetSpecific(CF_TSD_BAD_PTR);
        return;
    }
}
复制代码

咱们能够看到,table 会循环遍历 data 和 destructors 的数据,而且把 old 变量做为 destructors 里函数的参数。

这就是线程退出时,会调用到 Run Loop 销毁函数的缘由。

同时也因为 table 是从 0 开始遍历,因此会根据枚举值的大小,来决定销毁调用的顺序的。

咱们能够在 CFInternal.h 中找到相关枚举定义:

// Foundation uses 20-40
// Foundation knows about the value of __CFTSDKeyAutoreleaseData1
enum {
		__CFTSDKeyAllocator = 1,
		__CFTSDKeyIsInCFLog = 2,
		__CFTSDKeyIsInNSCache = 3,
		__CFTSDKeyIsInGCDMainQ = 4,
		__CFTSDKeyICUConverter = 7,
		__CFTSDKeyCollatorLocale = 8,
		__CFTSDKeyCollatorUCollator = 9,
		__CFTSDKeyRunLoop = 10,
		__CFTSDKeyRunLoopCntr = 11,
   		__CFTSDKeyMachMessageBoost = 12, // valid only in the context of a CFMachPort callout
   		__CFTSDKeyMachMessageHasVoucher = 13,
		// autorelease pool stuff must be higher than run loop constants
		__CFTSDKeyAutoreleaseData2 = 61,
		__CFTSDKeyAutoreleaseData1 = 62,
		__CFTSDKeyExceptionData = 63,
};

复制代码

注释里有一句 autorelease pool stuff must be higher than run loop constants 的说明,这一点就其实关系到 Run Loop 和 autorelease pool 释放的顺序了。

OSAtomicCompareAndSwapPtrBarrier

/*! @abstract Compare and swap for <code>int</code> values. @discussion This function compares the value in <code>__oldValue</code> to the value in the memory location referenced by <code>__theValue</code>. If the values match, this function stores the value from <code>__newValue</code> into that memory location atomically. This function is equivalent to {@link OSAtomicCompareAndSwap32}. @result Returns TRUE on a match, FALSE otherwise. */
OSATOMIC_DEPRECATED_REPLACE_WITH(atomic_compare_exchange_strong)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0)
bool	OSAtomicCompareAndSwapInt( int __oldValue, int __newValue, volatile int *__theValue );

复制代码

这一个函数,它首先对 oldValue , theValue 进行比较.

若是两个值相等,就执行 theValue = newValue,并返回 YES.

若是两个值不等,返回 NO .

值得注意的是,这个函数引入了 barrier,它的操做是原子的。 这是一个典型的 CAS 操做,无独有偶,在 RAC 中的一个特色也是使用 Atomic Operations ,完成线程同步。

它在CFRunLoopGet0的做用是 : 比较 CFRunLoops 是否为 null 。 若是为 null (第一次建立)了,就把 dict 赋值给 CFRunLoops 。若是不为 null,就释放掉 dict 。

memory barrier

这里再稍微提一下 barrier , 上面说它保证了原子操做。

memory barrier 在维基的定义是:

内存屏障(英语:Memory barrier),也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操做中的一个同步点,使得此点以前的全部读写操做都执行后才能够开始执行此点以后的操做。 大多数现代计算机为了提升性能而采起乱序执行,这使得内存屏障成为必须。

而对于 Objective C 的实现来讲,几乎全部的加锁操做最后都会设置 memory barrier ,官方文档的解释:

Note: Most types of locks also incorporate a memory barrier to ensure that any preceding load and store instructions are completed before entering the critical section.

为了防止编译器对咱们的代码作优化,改变咱们代码的指令顺序,能够采用 barrier 设置对咱们的代码顺序作保证。

CFRunLoopCreate

讲完 Run Loop 怎么获取,再看 Run Loop 怎么建立。

对于 CFRunLoopCreate :

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
	return NULL;
    }
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

复制代码

大致说一下建立的流程:

  1. 经过 CFRuntimeCreateInstance ,建立一个 CFRunLoopRef 实例。
  2. 对 loop 作初始化设置,好比唤醒端口,commonModes 等的设置。

注意: CFRunLoopPushPerRunData 会在建立时作一些初始化设置, __CFPortAllocate() 会设置唤醒的端口, CFRunLoopSetIgnoreWakeUps 调用的缘由时,目前处于唤醒状态,对它的消息作忽略。 HALT 命令能够中止系统运行,假如 wakeUpPort 为 CFPORT_NULL

CFRuntimeCreateInstance

真正建立获得 CFRunLoopRef 类型的 loop ,调用的是 CFRuntimeCreateInstance 来建立的。

它是一个用来建立 CF 实例类型的函数:

CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);
复制代码

更具体的解释能够查看 CFRuntime.h 对它的定义。

CFRunLoopCreate 给它传入了一个默认的分配器 kCFAllocatorSystemDefault ,一个 CFRunLoopGetTypeID() ,一个 size 。

CFRunLoopGetTypeID() 的操做以下:

CFTypeID CFRunLoopGetTypeID(void) {
    static dispatch_once_t initOnce;
    dispatch_once(&initOnce, ^{ 
    __kCFRunLoopTypeID = _CFRuntimeRegisterClass(&__CFRunLoopClass); 
    __kCFRunLoopModeTypeID = _CFRuntimeRegisterClass(&__CFRunLoopModeClass); 
    });
    return __kCFRunLoopTypeID;
}

复制代码

它在里面注册了 CFRunLoopClass 和 CFRunLoopModeClass 的类型,并用返回值,给对应的 typeID 赋值。做为单例,只运行一次。

size 的计算为 :

uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
复制代码

size 是一个 CFRunLoop 类型自己的大小,减掉 CFRuntimeBase 类型的大小获得的结果。

为何要减去一个 CFRuntimeBase 的类型大小?

查看 CFRuntime.c 对源码,发现里面会把减掉的 sizeof(CFRuntimeBase) 再给加回来:

CFIndex size = sizeof(CFRuntimeBase) + extraBytes + (usesSystemDefaultAllocator ? 0 : sizeof(CFAllocatorRef));

复制代码

CFRunLoopFindMode

static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) 
复制代码

CFRunLoopFindMode 是一个用来查找 mode 的函数,同时也能够来建立 mode 。

它其中有利用两个宏,来对 timer 的种类进行判断.查阅了一下定义:

#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
复制代码

也就是说,在 MACOSX 下,同时还会有使用 dispatch timer 来作定时器。而 MK_TIMER 是两个平台下都有的。

函数的大致逻辑是先判断有无,有就返回. 没有的话,就根据 create 的值决定是否新建立一个 mode .

在 CFRunLoopCreate 里面,调用的代码是

CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true) 
复制代码

它会新建立一个 mode 返回。

运行 Run Loop

启动 Run Loop 有 2 个函数,一个是 CFRunLoopRun , 一个是 CFRunLoopRunInMode

  • DefaultMode 启动
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
复制代码

注:1.0e10,这个表示1.0乘以10的10次方,这个参数主要是规定RunLoop的时间,传这个时间,表示线程常驻。

主线程的RunLoop调用函数,就是使用了 CFRunLoopRun

  • 指定 Mode 启动,容许设置RunLoop超时时间
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
复制代码

查看上面两个启动 Run Loop 运行的函数实现,发现都使用了 CFRunLoopRunSpecific .

CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    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;

	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);
    return result;
}

复制代码

1.经过 runloop 的 modeName 查找当前 mode。由于 CFRunLoopFindMode 的 create 参数为 false , 若是没找到,直接为 null ,不会建立新的 mode.

2.若是当前 mode 为空,函数结束,返回 CFRunLoopRunFinished .

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

3.若是当前 mode 存在,作一些赋值操做 .

4.向观察者发送 kCFRunLoopEntry 的消息,即将进入 RunLoop .

5.进入 CFRunLoopRun 函数,在这里作一系列观察和操做。

6.向观察者发送 kCFRunLoopExit 的消息,即将退出 RunLoop .

关于 CFRunLoopRun 在运行的时候,有 4 个枚举值表示它的状态:

/* Reasons for CFRunLoopRunInMode() to Return */
enum {
    kCFRunLoopRunFinished = 1,//结束
    kCFRunLoopRunStopped = 2,//暂停
    kCFRunLoopRunTimedOut = 3,//超时
    kCFRunLoopRunHandledSource = 4 //执行事件
};

复制代码

CFRunLoopDoObservers

CFRunLoopDoObservers 的官方文档说明以下:

A CFRunLoopObserver provides a general means to receive callbacks at different points within a running run loop. In contrast to sources, which fire when an asynchronous event occurs, and timers, which fire when a particular time passes, observers fire at special locations within the execution of the run loop, such as before sources are processed or before the run loop goes to sleep, waiting for an event to occur. Observers can be either one-time events or repeated every time through the run loop’s loop.

Each run loop observer can be registered in only one run loop at a time, although it can be added to multiple run loop modes within that run loop.

一个 CFRunLoopObserver 提供了一个通用的方法,在不一样的时机去接受运行中的 runloop 的回调。与在一个异步事件发生时触发的源,和在特定时间以后触发的定时器相比,在 run loop 执行的过程当中, 观察者会在特定的位置发送信号,例如 sources 执行以前活着 run loop 将要休眠以前,等待事件的发生. 观察者能够是一次性的,或者在经过每次 run loop 的循环里重复。

每一个 run loop 观察者只能在 run loop 中注册一次,尽管它能够添加到该 run loop 内的多个 run loop mode 中。

CFRunLoopRun

这里其实有两个 CFRunLoopRun 函数,一个是暴露给咱们在外面使用的,不带参数的:

CF_EXPORT void CFRunLoopRun(void);
复制代码

如今要说的,是这一个:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));
复制代码

由于函数比较长,因此分段来进行讲解.

1.runloop 状态判断 / GCD 队列的端口设置:

//获取开始时间
    uint64_t startTSR = mach_absolute_time();

    //对 runloop 状态作判断,检查是否处于 stop 的状况
    if (__CFRunLoopIsStopped(rl)) {
           __CFRunLoopUnsetStopped(rl);
	       return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	       rlm->_stopped = false;
	       return kCFRunLoopRunStopped;
    }
    
    //声明 dispatchPort 变量,做为一个 mach_port 通讯的端口,初始化值为 MACH_PORT_NULL
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    
    // 检测是否在主线程 && ( (是队列发的消息&&mode为null)||(不是队列发的消息&&不在主队列))
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    
    //若是是队列安全的,而且是主线程runloop,设置它对应的通讯端口
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
复制代码
  • HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY
#define HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY 0
复制代码

这里对于它的定义是 0 ,写死了。我猜想应该仍是有一个函数去作判断的。

目前只能从字面意思猜想,表明是否只分发 dispatch 消息的

  • _dispatch_get_main_queue_port_4CF
#define _dispatch_get_main_queue_port_4CF _dispatch_get_main_queue_handle_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 所关联的的端口。

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

#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
复制代码

3.根据超时 seconds 的时长,作对应操做。

dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    //小于等于 0 ,片刻的超时(instant timeout),直接设置为 0 ,不超时
    if (seconds <= 0.0) { 
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {//在限制的超时间隔内
    //根据是否为主线程,设置队列是主队列仍是后台队列
	dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
	//建立一个 GCD Timer
	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);
	//把 timer 和 context 给关联起来
	dispatch_set_context(timeout_timer, timeout_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 { 
    	 //无限期超时
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
复制代码

4.进入 do - while 循环,直到 reVal 不为 0 。如下代码为更好理解,删去 windows 相关:

// 设置判断是否为最后一次 dispatch 的端口通讯的变量
    Boolean didDispatchPortLastTime = true;
    // 设置一个结果变量,最后为几个 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
		 //声明一个 msg_buffer 数组
        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;

		 // 声明一个类型为 CFPortSet 的 waitSet, 值为 run loop mode 里的 portSet.
		 __CFPortSet waitSet = rlm->_portSet;

 		//将 run loop 从忽略唤醒消息的状态 unset ,开始接受唤醒消息
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
		 // 2. 通知 observers , Run Loop 即将触发 Timer 回调。
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3. 通知 observers , Run Loop 即将触发 Source0 (非 mach port) 回调。
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
		 // 执行 block
	     __CFRunLoopDoBlocks(rl, rlm);
		//  4. 执行 Source0 (非 mach port) 。
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {// 执行完 source0 后,假如还有须要执行的,再执行一次 block
            __CFRunLoopDoBlocks(rl, rlm);
		}

		// poll 变量,是否处理 source 或未超时
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
	
        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 而后跳转去处理消息(handle_msg)。
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }

        }


    didDispatchPortLastTime = false;
    
	// 6.通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
	// 注意到若是实际处理了 source0 或者超时,不会进入睡眠,因此不会通知
	if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
	
	// 设置标志位, Run Loop 休眠
	__CFRunLoopSetSleeping(rl);

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

// 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 {
            if (kCFUseCollectableAllocator) {//假若有kCFUseCollectableAllocator分配器,使用 memset 清空msg_buffer
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            
            // 设置 mach port 通讯,会睡眠线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            // 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) {//假如 _timerFired 为真,把 livePort 做为队列端口,在以前服务于 timers
                    rlm->_timerFired = false;
                    //退出
                    break;
                } else {// _timerFired 为假, 而且 msg 存在不为 msg_buffer, 释放 msg
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            } else {
                //退出
                break;
            }
        } while (1);
        
#else // 不在 MACOSX 中
        if (kCFUseCollectableAllocator) {//若是 kCFUseCollectableAllocator 分配器,使用 memset 清空 msg_buffer
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        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));

		//对 waitSet 里的 dispatchPort 端口作移除
        __CFPortSetRemove(dispatchPort, waitSet);
        
        //让 Run Loop 忽略唤醒消息,由于已经从新在运行了
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again
		__CFRunLoopUnsetSleeping(rl);
		
	     // 8. 通知 observers: kCFRunLoopAfterWaiting, 线程刚被唤醒
        // 注意实际处理过 source 0 或者已经超时的话,不会通知(由于没有睡)
		if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
		
		//处理对应唤醒的消息
        handle_msg:;
        
        //将 Run Loop 从新忽略唤醒消息,由于已经从新在运行了
        __CFRunLoopSetIgnoreWakeUps(rl);


        if (MACH_PORT_NULL == livePort) {// livePort 为空,什么事都不作
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {// livePort 等于 run loop 的 _wakeUpPort
            // 被 CFRunLoopWakeUp 函数唤醒的
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();

        }
        
// 在 MACOSX 里
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {//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 
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {//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) {// livePort 等于 dispatchPort
        	 // 9.2 若是有dispatch到main_queue的block,执行block
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            
            //解锁
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            //设置 CFTSDKeyIsInGCDMainQ 位置的 TSD 为 6 .
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
			
			// 处理 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 {
            //9.3 被 source (基于 mach port) 唤醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            
            // 假如咱们 从这个 mach_msg 中接收到一个 voucher,而后在 TSD 中放置一个复制的新的 voucher.
            // CFMachPortBoost 会在 TSD 中去查找这个 voucher. 
            // 经过使用 TSD 中的值,咱们将 CFMachPortBoost 绑定到这个接收到的 mach_msg 中,在这两段代码之间没有任何机会再次设置凭证
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            
            if (rls) {//若是 rls 存在
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
				mach_msg_header_t *reply = NULL;
				//处理 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);
				}

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

   // 执行加入到Loop的block
	__CFRunLoopDoBlocks(rl, rlm);
        
        
	//根据一次循环后的状态,给 retVal 赋值 。状态不变则继续循环
	if (sourceHandledThisLoop && stopAfterHandle) {
	    retVal = kCFRunLoopRunHandledSource;
	    
   } else if (timeout_context->termTSR < mach_absolute_time()) {
        retVal = kCFRunLoopRunTimedOut;
        
	} else if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	    
	} else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
	} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
	    retVal = kCFRunLoopRunFinished;
	}
        
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
		// 循环一次后收尾处理
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif

    } while (0 == retVal);

复制代码

上面的代码,有几个地方的定义可能须要结合其它地方才能理解:

  • voucher_mach_msg_state_t 在 mach.h 中:
/*! * @typedef voucher_mach_msg_state_t * * @abstract * Opaque object encapsulating state changed by voucher_mach_msg_adopt(). */
typedef struct voucher_mach_msg_state_s *voucher_mach_msg_state_t;
复制代码

不透明的对象封装状态由 voucher_mach_msg_adopt() 改变,它表明一种 mach_msg 通讯时的状态。

  • HANDLE:
DISPATCH_EXPORT HANDLE _dispatch_get_main_queue_handle_4CF(void);
复制代码

返回做为主队列相关联的 run loop 。

  • memset :做用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操做的一种最快方法。

5.释放 timerout_timer 定时器相关

if (timeout_timer) {//若是存在,取消并释放
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {//不存在,将对应的 timeour_context 释放
        free(timeout_context);
    }
    //结束返回 retVal 状态。
    return retVal;
复制代码

CFRunLoopServiceMachPort

这个函数是让线程休眠的关键,它在里面作了和 mach port 相关的操做。

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 ... */
    
        // msg 相关数据设置
        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;
        
        // 根据 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);

      
        // 在 mach_msg 以后注意全部 voucher 相关的正常运行
        // 假如咱们没有释放前面的 voucher , 将会出现内存泄漏
        voucher_mach_msg_revert(*voucherState);
        
        // 会有调用者去负责调用 voucher_mach_msg_revert .它会让接收到的 voucher 变成当前的这一个值。
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
 
                // 调用者 在这里 请求了一个 voucher 的复制的值。经过在 mach_msg 先后作这个操做,咱们确保在 mach_msg 返回和使用 voucher 的复制值的时候,没有涉及设置 voucher 的值。
                // 为确保 CFMachPortBoost 使用的 voucher ,因此咱们只在 voucher 不是 state_unchanged 的时候,去设置 TSD 。
                *voucherCopy = voucher_copy();
            } else {
            	//值为 VOUCHER_MACH_MSG_STATE_UNCHANGED ,置为 null
                *voucherCopy = NULL;
            }
        }

	    // 唤醒 Run Loop
        CFRUNLOOP_WAKEUP(ret);
        
        if (MACH_MSG_SUCCESS == ret) {// ret 成功后,设置 livePort 的值,返回 true
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {// ret 超时,释放 msg ,有关变量置空,返回 false
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        //ret 不为 MACH_RCV_TOO_LARGE ,退出循环
        if (MACH_RCV_TOO_LARGE != ret) break;
        //ret 为 MACH_RCV_TOO_LARGE,作释放操做,从新进入循环
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

复制代码

这里有查询 CFRUNLOOP_SLEEP() 和 CFRUNLOOP_POLL() 等函数,都是 do { } while (0) 这样的宏,没有真正实现代码,因此没法再看到具体的状况。

总结

这一次学习的过程,最大的感触,就是对于知识的相通性。

例如对于 TSD 线程私有数据的理解,搜寻不少跟 iOS 有关资料都找不到说明,最后是在 unix 相关的文章才看到解释。还有 Event Loop 的机制在其它平台等实现。

固然整个过程比较枯燥,阅读的量也比较大,须要耐心。

比较遗憾的是,有一些地方,苹果并无给出具体的代码实现或者明确的解释。

本人水平有限,若有错误和值得商榷的地方,欢迎你们拍砖。

参考

前端思惟转变--从事件驱动到数据驱动

苹果文档--RunLoop

CFRunLoop.c

CFPlatform.c

深刻理解RunLoop

XNU 源码

线程私有数据

UNIX环境高级编程——线程私有数据

相关文章
相关标签/搜索