文章目录
  1. 1. 事件循环与RunLoop
    1. 1.1. macOS/iOS中的RunLoop
    2. 1.2. RunLoop和线程
  2. 2. RunLoop 源码分析
    1. 2.1. 获取Runloop
    2. 2.2. RunLoop的循环逻辑
  3. 3. Runloop底层实现
    1. 3.1. Mach
    2. 3.2. Runloop与Mach
  4. 4. CFRunLoop的组成
    1. 4.1. CFRunLoopSourceRef
    2. 4.2. CFRunLoopTimerRef
    3. 4.3. CFRunLoopObserverRef
    4. 4.4. commonModes说明和应用举例
  5. 5. CFRunLoop的对外接口
    1. 5.1. 常用的对外接口
    2. 5.2. 使用中的注意点
  6. 6. RunLoop的状态和回调
    1. 6.1. app静止时的RunLoop状态
    2. 6.2. 回调
  7. 7. 参考

事件循环与RunLoop

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,这个机制通常被称作 event loop。他的代码逻辑一般是这样的:

1
2
3
4
5
6
7
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}

Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。这个模型的主要作用在于:管理事件/消息,让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

macOS/iOS中的RunLoop

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

OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。

  • CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
  • NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

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 源码分析

CFRunLoop是开源的可以在 OpensourceApple-CF下载整个CoreFoundation代码查看

获取Runloop

我们不能直接创建 RunLoop,它只提供了两个自动获取的函数: CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。

下面是两个函数的相关代码

1
2
3
4
5
6
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;
}
1
2
3
4
5
6
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}

可以看到,这两个函数都调用了_CFRunLoopGet0() 参数都是当前的线程,可见Runloop和线程是对应的,下面是内部函数CFRunLoopGet0的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;

// 访问 __CFRunLoops 时的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;

// 获取一个 pthread 对应的 RunLoop。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//如果传入空线程,则将参数置为主线程,从而保证函数总是有效
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
//第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
__CFSpinUnlock(&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);
__CFSpinLock(&loopsLock);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);

if (!loop) {
// 取不到时,创建一个
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//将创建的runloop存入字典
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}

__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}

//注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
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;

从上面的源码可以得到下面的结论,这么规则对我们的开发有很大帮助

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

RunLoop的循环逻辑

CFRunLoopRun()函数用于启动Runloop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// 用DefaultMode启动
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode,
1.0e10,
false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

/// 用指定的Mode启动,允许设置RunLoop超时时间
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
modeName,
seconds,
returnAfterSourceHandled);
}

他们都调用了 CFRunLoopRunSpecific()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
CHECK_FOR_FORK();
/// 正在被销毁(deallicating)
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;

__CFRunLoopLock(rl);

// 根据mode name 找到对应的 mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

// 如果没找到对应 mode 或者 mode的items为空
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
// 找到对应的mode,但是 mode items 为空,尝试解锁mode
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
// 结束runloop
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}

// 保存当前 Mode 数据
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// 保存当前Mode
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;

// 通知 Observers: RunLoop 即将进入 loop。
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 内部函数,进入runloop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知 Observers: RunLoop 即将退出。
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

__CFRunLoopPopPerRunData(rl, previousPerRun);
// 恢复Mode
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}

__CFRunLoopRun是RunLoop的核心内部函数它的源码比较冗长,会单写一篇文章来分析这里只给出分析结果

__CFRunLoopRun 内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里直到进程被终止。

Runloop底层实现

iOS的核心是XUN内核,XUN由三部分组成:Mach、BSD、IOKit。其中mach 提供了诸如处理器调度、IPC (进程间通信)等基础服务是XUN内核的内环。

Mach

Mach是一套IPC系统,它以下面4个概念为基础:

  • 任务(task):即拥有一组系统资源的对象,允许“线程”在其中执行。
  • 线程(thread):是执行的基本单位,拥有一个任务的上下文,并且共享任务中的资源。
  • 端口(port):是任务间通讯的一组受保护的消息队列;任务可以对任何port发送或接收数据。
  • 消息(message):是某些有类型的数据对象的集合,它们只可以发送至port - 而非某特定任务或线程。

在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为”对象”。但是 Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。”消息”是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach 的 IPC (进程间通信) 的核心

Mach 本身提供的 API 非常有限,而且苹果也不鼓励使用 Mach 的 API,但是这些API非常基础,如果没有这些API的话,其他任何工作都无法实施。这些API可在中查看.

下面是发送消息和接收消息的API,他们是同一个API,其中option标记了传递的方向。

1
2
3
4
5
6
7
8
extern mach_msg_return_t  mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

mach_msg实际是在内核中完成的,用户调用mach_msg()实际上是调用的mach_msg_trap(),这实际上是一个系统调用。

Runloop与Mach

下面是__CFRunLoopServiceMachPort的源码简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static Boolean __CFRunLoopServiceMachPort()
{
kern_return_t ret = KERN_SUCCESS;
for (;;) {
ret = mach_msg()
if (MACH_MSG_SUCCESS == ret) {
*livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
return true;
}
//...
}

HALT;
return false;
}

RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。如果在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方

CFRunLoop的组成

在 CoreFoundation 里面关于 RunLoop 有5个类(其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装):

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。

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

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

开源代码中 CFRunloop定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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; //commonModes set
CFMutableSetRef _commonModeItems; //commonMode's <Source/Observer/Timer> set
CFRunLoopModeRef _currentMode; // current runloop mode
CFMutableSetRef _modes;// mode set
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};

开源代码中CFRunloopMode的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0; //sources0 set
CFMutableSetRef _sources1; //sources1 set
CFMutableArrayRef _observers; // observer array
CFMutableArrayRef _timers; // timer array
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
//...
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

CFRunLoopSourceRef

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

  • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef

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

CFRunLoopObserverRef

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

1
2
3
4
5
6
7
8
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
};

commonModes说明和应用举例

通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中,一个 Mode 可以将自己标记为”Common”。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。

应用举例

主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。

CFRunLoop的对外接口

常用的对外接口

Mode 管理

1
2
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode); 
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);

Mode item 管理

1
2
3
4
5
6
7
8
9
10
11
Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode);

Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode);

Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

Block

1
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))

使用中的注意点

你只能通过 mode name 来操作内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。

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

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

RunLoop的状态和回调

app静止时的RunLoop状态

然后在 App 启动后处于静止状态时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。然后在控制台输入下面代码,可以看到当前RunLoop的状态:

1
po [NSRunLoop currentRunLoop]

下面是当前RunLoop的简要状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<CFRunLoop>{ wakeup port = 0x1e03, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = {
UITrackingRunLoopMode
kCFRunLoopDefaultMode
}
}

common mode items = {
// source 0
CFRunLoopSource {order = -1 context { version = 0, callout = PurpleEventSignalCallback} }
CFRunLoopSource {order = 0 context { version = 0, callout = FBSSerialQueueRunLoopSourceHandler}
CFRunLoopSource {order = -2 context { version = 0, callout = __handleHIDEventFetcherDrain}
CFRunLoopSource {order = -1 context { version = 0, callout = __handleEventQueue}

// source 1
CFRunLoopSource {order = -1 context { version = 1, callout = PurpleEventCallback}
CFRunLoopSource {order = 0 context { port = 24587 }
CFRunLoopSource {order = 0 context { port = 26883 }
CFRunLoopSource {order = 0 context { port = 18695 }
CFRunLoopSource {order = 0 context { port = 1d03, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}

/* observer */
// Entry
CFRunLoopObserver {order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler }
// BeforeWaiting (即将进入休眠)
CFRunLoopObserver {order = 0, activities = 0x20, callout = _UIGestureRecognizerUpdateObserver }

//BeforeWaiting | Exit
CFRunLoopObserver {order = 1999000, activities = 0xa0, callout = _beforeCACommitHandler }
CFRunLoopObserver {order = 2001000, activities = 0xa0, callout = _afterCACommitHandler }
CFRunLoopObserver {order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler }
CFRunLoopObserver {order = 2000000,
activities = 0xa0,
callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv }

}

modes {
CFRunLoopMode { name = UITrackingRunLoopMode}
CFRunLoopMode { name = GSEventReceiveRunLoopMode}
CFRunLoopMode { name = kCFRunLoopDefaultMode}
CFRunLoopMode { name = UIInitializationRunLoopMode}
CFRunLoopMode { name = kCFRunLoopCommonModes}
}

可以看到,系统默认注册了5个Mode:

  • kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  • UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
  • kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

回调

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

参考

深入理解RunLoop

OpenSource-CF

Mach-Wiki