Runloop(内部分享)

Let us runloop (内部分享)


Runloop 简介

runloop 就是 OSX/iOS 系统中的消息循环机制,类似Windows终端 Event loop (事件循环)。

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

CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。

NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

  • NSRunloop 用来管理系统的输入源事件的,包括窗口的鼠标(iOS上为触摸)和键盘事件、NSPort(线程间通信)、NSConnection(网络请求)以及NSTimer(含CADisplayLink)事件。
  • WARNING(苹果官方原文)

    The NSRunLoop class is generally not considered to be thread-safe and its methods should only be called within the context of the current thread. You should never try to call the methods of an NSRunLoop object running in a different thread, as doing so might cause unexpected results.
  • 警告(秀策分享译文)

    嘿,线程,我(NSRunLoop)没那么好脾气!并且只在特定的环境中(当前线程)才干活,你永远别想弄个其他环境(线程)勾搭我(调用方法),不然,就立马死给你看。

Runloop 特性

  • 不能手动创建,只能获取(传说中的不劳而获?🤗)。
  • 内部自带死循环(信runloop,得永生😇)。
  • 一个线程内部,寄生着唯一的一个runloop(寄生虫?🐛)。
  • 懒加载(你不说要,我就不给🤓)。
  • 一个runloop可以有若干的Mode(运行模式:),但每次只能在一个Mode下运行。

  • 如果mode(无source,timer,observer)为空,runloop将直接退出(不给工资就闪人🙄)。
  • runloop内部的Mode 只能增加,不能删除。

Runloop 逻辑(网友整理版)

1432798974517485.png

Runloop 作用

  • 保持程序的持续运行。
  • 处理事件任务。
  • 提高系统性能,节省CPU资源。
  • ……

Runloop 代码实例

  • 任务延时加载
  • 线程常驻内存
  • NSTimer定时器

Runloop 源码部分

Runloop 数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct __CFRunLoop {
CFRuntimeBase _base; // 设置runloop 基本数据状态:睡眠,释放(销毁),也用于计算内部mode内存空间
pthread_mutex_t _lock; // 线程锁
__CFPort _wakeUpPort; // 唤醒端口
Boolean _unused; // 是否可用
volatile _per_run_data *_perRunData; // 设置runloop(状态)数据:忽略唤醒,停止
pthread_t _pthread; // 线程
uint32_t _winthread; // windows 线程,非windows环境下,该字端始终为0
CFMutableSetRef _commonModes; // common mode 可变集合
CFMutableSetRef _commonModeItems; // common item 可变集合
CFRunLoopModeRef _currentMode; // 当前mode
CFMutableSetRef _modes; // mode 集合
struct _block_item *_blocks_head; // _block_item 链表头(任务链表)
struct _block_item *_blocks_tail; // _block_item 链表尾(任务链表)
CFTypeRef _counterpart; // runloop相关数据(void * 类型,相当于OC中的id类型),用于并发多线程 类似多线程内部的TSD数据
};

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

/**
* 创建runloop
*
* @param t 线程参数
*
* @return 创建好的runloop
*/

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
// 初始化 runloop 为空
CFRunLoopRef loop = NULL;
// 初始化 runloop mode
CFRunLoopModeRef rlm;
// 计算 runloop 实例的内存空间
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
// 调用runtime函数,创建实例
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
// 如果创建失败,直接返回
if (NULL == loop) {
return NULL;
}
// 压栈数据,初始化runloop的数据状态
(void)__CFRunLoopPushPerRunData(loop);
// 设置 runloop 的线程锁
__CFRunLoopLockInit(&loop->_lock);
// 设置 runloop 唤醒端口
loop->_wakeUpPort = __CFPortAllocate();
// 如果设置唤醒端口失败,终止并报错
if (CFPORT_NULL == loop->_wakeUpPort) HALT;
// 设置 runloop 的忽略唤醒
__CFRunLoopSetIgnoreWakeUps(loop);
// 创建common Mode 可变集合
loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
// 设置commom mode 值:kCFRunLoopDefaultMode
CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
// 初始化item
loop->_commonModeItems = NULL;
// 初始化 current Mode
loop->_currentMode = NULL;
// 创建 mode 可变集合
loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
// 初始化任务链表
loop->_blocks_head = NULL;
loop->_blocks_tail = NULL;
// 初始化 对等数据
loop->_counterpart = NULL;
// 根据传入的线程,设置 runloop 的线程
loop->_pthread = t;
// windows 环境
#if DEPLOYMENT_TARGET_WINDOWS
loop->_winthread = GetCurrentThreadId();
#else
// 非windows 环境
loop->_winthread = 0;
#endif
// 先查找mode ,如果没有,则根据参数(true)创建mode
rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
// 去除 runloop mode 的同步锁
if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
// 返回 runloop
return loop;
}

获取Runloop方法:

CFRunLoopGetMain 获取主线程的runloop
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;
}
CFRunLoopGetCurrent 获取当前线程的runloop
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方法
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 获取 runloop 方法
*
* @param t 线程参数
*
* @return 返回获得的 runloop
*/

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 放错机制,如果线程未nil,则默认为主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
// 设置互斥锁
__CFSpinLock(&loopsLock);
// 如果全局字典为空
if (!__CFRunLoops) {
// 去除互斥锁
__CFSpinUnlock(&loopsLock);
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建 主线程的 mainLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存 mainLoop 到字典中: key 为线程,value为mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 使用 dict 设置全局字典__CFRunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
// 释放 dict 的内存
CFRelease(dict);
}
// 内存管理,释放mainLoop
CFRelease(mainLoop);
// 设置互斥锁
__CFSpinLock(&loopsLock);
}
// 根据线程,从全局字典中获取 runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 取消互斥锁
__CFSpinUnlock(&loopsLock);
// 容错判断,如果获取的 runloop 为空,则创建新的 runloop
if (!loop) {
// 创建新的 runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
// 互斥锁
__CFSpinLock(&loopsLock);
// 因为之前对互斥锁进行取消,所以再次添加互斥锁后,为保证数据准确性,再次根据线程参数,从全局字典中获取 runloop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 如果全局字典中的 runloop 最新值仍为空
if (!loop) {
// 使用新的 runloop 并更新全局字典
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
// 去除互斥锁
__CFSpinUnlock(&loopsLock);
// 释放内存
CFRelease(newLoop);
}
// 注册线程与runloop的对应关系
// 网上资料对下面的代码解释为注册一个回调方法,是错误的
if (pthread_equal(t, pthread_self())) {
// __CFTSDKeyRunLoop : 枚举常量,数值为10,在 TSD 表中标识当前线程
// __CFTSDKeyRunLoopCntr :枚举常量,数值为11,在 TSD 表中标识主线程
// _CFSetTSD 的第一个参数,数值不能大于70,否则会报错,这个函数主要用于设置线程内部的数据
// TSD表中,每个数据都都需要配置销毁器对象
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}