借助AI辅助。
函数概述
__CFRunLoopDoBlocks 是 RunLoop 中负责执行 block 的核心函数。它处理通过 CFRunLoopPerformBlock 添加到 RunLoop 中的异步 blocks,这些 blocks 会在 RunLoop 的每次循环中被执行。
函数签名
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm)
参数说明
-
CFRunLoopRef rl: 当前运行的 RunLoop
-
CFRunLoopModeRef rlm: 当前的 RunLoop Mode
返回值
-
Boolean: 如果至少执行了一个 block 返回 true,否则返回 false
前置条件
-
函数调用时必须持有锁:
rl 和 rlm 都必须处于加锁状态
-
函数返回时保持锁状态: 出口时
rl 和 rlm 仍然加锁
Block Item 数据结构
struct _block_item {
struct _block_item *_next; // 链表的下一个节点
CFTypeRef _mode; // 可以是 CFStringRef 或 CFSetRef
void (^_block)(void); // 要执行的 block
};
RunLoop 中的 Block 链表
rl->_blocks_head --> [Block1] -> [Block2] -> [Block3] -> NULL
^ ^
| |
(first) (last)
|
rl->_blocks_tail
完整代码逐行注释
// 📝 调用此函数时,rl 和 rlm 必须已加锁
// 函数返回时,rl 和 rlm 仍然保持加锁状态
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
// ==================== 第一部分:性能追踪和前置检查 ====================
// 📊 记录性能追踪点:开始执行 blocks
// 可通过 Instruments 的 kdebug 工具查看此事件
cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_BLOCKS | DBG_FUNC_START, rl, rlm, 0, 0);
// 🚪 快速退出1:如果 RunLoop 中没有待处理的 blocks
// _blocks_head 为 NULL 表示链表为空,直接返回 false(表示没有执行任何 block)
if (!rl->_blocks_head) return false;
// 🚪 快速退出2:如果 mode 无效或没有名称
// 这是一个防御性检查,正常情况下不应该发生
if (!rlm || !rlm->_name) return false;
// 标志位:记录是否至少执行了一个 block
Boolean did = false;
// ==================== 第二部分:摘取整个 Block 链表 ====================
// 保存链表头指针到局部变量
struct _block_item *head = rl->_blocks_head;
// 保存链表尾指针到局部变量
struct _block_item *tail = rl->_blocks_tail;
// 🎯 清空 RunLoop 的 blocks 链表头
// 将所有 blocks "取出"到局部变量中(摘取操作)
rl->_blocks_head = NULL;
// 🎯 清空 RunLoop 的 blocks 链表尾
// 此时 RunLoop 中已经没有 blocks 了
rl->_blocks_tail = NULL;
// ⚠️ 为什么要清空 RunLoop 的链表?
// 1. 避免在执行 block 期间,其他代码再次访问这些 blocks
// 2. 允许 block 执行期间添加新的 blocks(不会与当前正在处理的 blocks 冲突)
// 3. 未执行的 blocks 稍后会被重新添加回 RunLoop
// 获取 RunLoop 的 commonModes 集合
// commonModes 通常包含 kCFRunLoopDefaultMode 和 UITrackingRunLoopMode
CFSetRef commonModes = rl->_commonModes;
// 获取当前 mode 的名称(如 "kCFRunLoopDefaultMode")
CFStringRef curMode = rlm->_name;
// ==================== 第三部分:解锁(准备执行 blocks)====================
// 🔓 解锁 RunLoop Mode
__CFRunLoopModeUnlock(rlm);
// 🔓 解锁 RunLoop
__CFRunLoopUnlock(rl);
// ⚠️ 为什么要解锁?
// 1. 防止死锁:block 中可能调用 RunLoop API(如 CFRunLoopPerformBlock)
// 2. 避免长时间持锁:block 可能执行耗时操作
// 3. 提高并发性:允许其他线程在 block 执行期间访问 RunLoop
// 🛡️ 安全性保证:
// - 已经将 blocks 链表"摘取"到局部变量 head/tail
// - 即使其他线程修改了 rl->_blocks_head,也不会影响本次执行
// - 新添加的 blocks 会形成新的链表,不会与当前正在处理的链表冲突
// ==================== 第四部分:遍历链表,执行符合条件的 blocks ====================
// 前驱节点指针(用于链表删除操作)
// 当删除节点时,需要修改前驱节点的 _next 指针
struct _block_item *prev = NULL;
// 当前遍历的节点,从头节点开始
struct _block_item *item = head;
// 遍历整个 blocks 链表
while (item) {
// 保存当前节点到 curr(因为 item 会被提前移动到下一个节点)
struct _block_item *curr = item;
// 🔜 提前移动到下一个节点
// 原因:如果 curr 被删除(执行并释放),item 仍然指向有效的下一个节点
// 避免在删除节点后访问已释放的内存
item = item->_next;
// 标志位:当前 block 是否应该在当前 mode 下执行
Boolean doit = false;
// ---------- 判断 Block 是否应该在当前 Mode 下执行 ----------
// 🔍 情况1:_mode 是 CFString 类型(单个 mode)
// CFGetTypeID() 获取对象的类型ID,_kCFRuntimeIDCFString 是 CFString 类型的常量ID
if (_kCFRuntimeIDCFString == CFGetTypeID(curr->_mode)) {
// 判断逻辑(两种情况任一成立即可):
// 条件1: CFEqual(curr->_mode, curMode)
// block 指定的 mode 与当前 mode 完全匹配
// 例如:block 添加到 "kCFRunLoopDefaultMode",当前也是 "kCFRunLoopDefaultMode"
// 条件2: CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)
// block 添加到 "kCFRunLoopCommonModes" 且当前 mode 在 commonModes 集合中
// 例如:block 添加到 "kCFRunLoopCommonModes",当前是 "kCFRunLoopDefaultMode"
// 且 commonModes 包含 "kCFRunLoopDefaultMode",则应该执行
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
} else {
// 🔍 情况2:_mode 是 CFSet 类型(多个 modes 的集合)
// 判断逻辑(两种情况任一成立即可):
// 条件1: CFSetContainsValue((CFSetRef)curr->_mode, curMode)
// block 指定的 modes 集合中包含当前 mode
// 例如:block 添加到 {"Mode1", "Mode2"},当前是 "Mode1"
// 条件2: CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)
// block 指定的 modes 集合中包含 "kCFRunLoopCommonModes"
// 且当前 mode 在 commonModes 集合中
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
// ---------- 处理不执行的 Block ----------
// 如果当前 block 不需要执行:
// 更新 prev 指针,指向当前节点
// 这个节点会被保留在链表中(稍后重新添加回 RunLoop)
if (!doit) prev = curr;
// ---------- 处理需要执行的 Block ----------
if (doit) {
// 当前 block 需要在当前 mode 下执行
// ===== 子部分1:从链表中移除当前节点 =====
// 如果有前驱节点,将前驱节点的 next 指针指向下一个节点
// 跳过当前节点(curr),实现删除
// 示例:prev -> curr -> item 变为 prev ---------> item(删除 curr)
if (prev) prev->_next = item;
// 如果当前节点是头节点,更新头指针
// 新的头节点变成下一个节点
if (curr == head) head = item;
// 如果当前节点是尾节点,更新尾指针
// 新的尾节点变成前驱节点
if (curr == tail) tail = prev;
// ===== 子部分2:提取 Block 信息并释放节点内存 =====
// 提取 block 闭包(复制指针)
// 类型:void (^)(void) 表示无参数无返回值的 block
void (^block)(void) = curr->_block;
// 释放 mode 对象(CFString 或 CFSet)
// 减少引用计数,可能触发对象销毁
CFRelease(curr->_mode);
// 释放节点结构体的内存(C 风格内存管理)
// 此时 curr 指针已无效,不能再访问
free(curr);
// ===== 子部分3:执行 Block =====
// ⚠️ 这里的 if (doit) 是冗余的(外层已经检查过)
// 可能是历史遗留代码或防御性编程
if (doit) {
// 🔄 开始自动释放池(ARP = AutoRelease Pool)
// 管理 block 执行期间创建的临时对象
CFRUNLOOP_ARP_BEGIN(rl);
// 📊 记录性能追踪点:开始调用 block
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_BLOCK | DBG_FUNC_START, rl, rlm, block, 0);
// ⚡ 执行 block 的核心宏
// 展开后通常是:block();
// 这是整个函数的核心目的!
// ⚠️ Block 执行期间可能发生:UI 更新、网络请求、数据库操作、
// 再次调用 CFRunLoopPerformBlock、操作 RunLoop、长时间阻塞等
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
// 📊 记录性能追踪点:结束调用 block
// 可计算 block 执行耗时 = end - start
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_BLOCK | DBG_FUNC_END, rl, rlm, block, 0);
// 🔄 结束自动释放池
// 释放 block 中创建的所有 autorelease 对象
CFRUNLOOP_ARP_END();
// ✅ 标记:至少执行了一个 block
did = true;
}
// ===== 子部分4:释放 Block =====
// 释放 block 对象(减少引用计数)
// block 可能会被销毁,触发其捕获变量的释放
// 💡 为什么在重新加锁之前释放?
// 注释原文:"do this before relocking to prevent deadlocks
// where some yahoo wants to run the run loop reentrantly
// from their dealloc"
// 原因:block 的 dealloc 可能触发捕获变量的析构函数,
// 某些"聪明人"可能在 dealloc 中重入 RunLoop,
// 如果此时持有锁,会导致死锁。
// 在解锁状态下释放 block,即使 dealloc 尝试操作 RunLoop,
// 也不会因为已持有锁而死锁
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
}
}
// ==================== 第五部分:重新加锁 ====================
// 🔒 重新锁定 RunLoop
__CFRunLoopLock(rl);
// 🔒 重新锁定 RunLoop Mode
__CFRunLoopModeLock(rlm);
// ✅ 恢复函数入口时的锁状态
// 满足函数约定:"Call with rl and rlm locked"
// ==================== 第六部分:将未执行的 Blocks 放回 RunLoop ====================
// 如果还有未执行的 blocks(链表不为空)
// head 和 tail 现在指向未执行的 blocks 链表(已执行的 blocks 已从链表中移除)
if (head && tail) {
// 将未执行的链表的尾部连接到 RunLoop 当前的 blocks 链表头部
// 示例:
// 未执行的链表:[Block1] -> [Block2] -> NULL (head=Block1, tail=Block2)
// RunLoop 当前链表:[Block3] -> [Block4] -> NULL (rl->_blocks_head=Block3)
// 连接后:[Block1] -> [Block2] -> [Block3] -> [Block4] -> NULL
tail->_next = rl->_blocks_head;
// 更新 RunLoop 的 blocks 链表头指针
// 指向未执行的链表的头部
rl->_blocks_head = head;
// 如果 RunLoop 当前没有尾指针(即 _blocks_head 原本为 NULL)
// 则将尾指针设置为未执行链表的尾部
// ⚠️ 注意:如果 rl->_blocks_tail 已经存在,不更新它
// 因为新的尾节点应该是原来 RunLoop 链表的尾节点
// (未执行的链表已经通过 tail->_next 连接到了原链表前面)
// 📊 重新插入的顺序:未执行的 blocks 被放在队列的最前面,
// 在执行期间新添加的 blocks 排在后面,
// 这保证了未执行的 blocks 在下次循环中优先被执行
if (!rl->_blocks_tail) rl->_blocks_tail = tail;
}
// ==================== 第七部分:结束性能追踪 ====================
// 📊 记录性能追踪点:完成 blocks 执行
cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_BLOCKS | DBG_FUNC_END, rl, rlm, 0, 0);
// 返回是否至少执行了一个 block
// true:执行了至少一个 block
// false:没有执行任何 block(所有 blocks 的 mode 都不匹配)
return did;
}
关键设计要点
1. Mode 匹配逻辑
Block 的 mode 可以是两种类型:
类型1:CFString(单个 mode)
// 添加到特定 mode
CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, ^{
NSLog(@"Execute in default mode only");
});
// 添加到 common modes
CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{
NSLog(@"Execute in all common modes");
});
匹配规则:
- 精确匹配:
block.mode == currentMode
- Common modes 匹配:
block.mode == kCFRunLoopCommonModes && currentMode ∈ commonModes
类型2:CFSet(多个 modes)
// 添加到多个 modes
CFSetRef modes = CFSetCreate(NULL,
(const void *[]){kCFRunLoopDefaultMode, CFSTR("CustomMode")},
2,
&kCFTypeSetCallBacks);
CFRunLoopPerformBlock(runLoop, modes, ^{
NSLog(@"Execute in default or custom mode");
});
CFRelease(modes);
匹配规则:
- 集合包含:
currentMode ∈ block.modes
- Common modes 匹配:
kCFRunLoopCommonModes ∈ block.modes && currentMode ∈ commonModes
2. 链表操作详解
初始状态:
rl->_blocks_head --> [A] -> [B] -> [C] -> [D] -> NULL
^ ^
(match) (no) (no) (match)
执行后:
- A 和 D 已执行并释放
- B 和 C 未执行(mode 不匹配)
剩余链表:
head --> [B] -> [C] -> NULL
^ ^
prev tail
重新插入(假设期间添加了新 block E):
rl->_blocks_head --> [E] -> NULL
连接后:
rl->_blocks_head --> [B] -> [C] -> [E] -> NULL
3. 锁的管理策略
入口状态:rl 锁定 + rlm 锁定
↓
摘取 blocks 链表(持有锁)
↓
解锁 rl 和 rlm
↓
遍历并执行 blocks(无全局锁)
↓
重新锁定 rl 和 rlm
↓
放回未执行的 blocks(持有锁)
↓
出口状态:rl 锁定 + rlm 锁定
为什么这样设计?
| 阶段 |
锁状态 |
原因 |
| 摘取链表 |
加锁 |
保证原子性,防止并发修改 |
| 执行 blocks |
解锁 |
防止 block 中调用 RunLoop API 导致死锁 |
| 放回链表 |
加锁 |
保证原子性,防止链表结构损坏 |
4. 内存管理细节
// 节点创建(在 CFRunLoopPerformBlock 中)
struct _block_item *item = malloc(sizeof(struct _block_item));
item->_mode = CFRetain(mode); // 引用计数 +1
item->_block = Block_copy(block); // 引用计数 +1
// 节点销毁(在 __CFRunLoopDoBlocks 中)
CFRelease(curr->_mode); // 引用计数 -1
free(curr); // 释放节点内存
Block_release(block); // 引用计数 -1
引用计数管理:
-
CFRetain/CFRelease: 管理 mode 对象(CFString/CFSet)
-
Block_copy/Block_release: 管理 block 对象
-
malloc/free: 管理节点结构体
5. 避免死锁的设计
Block_release(block); // do this before relocking to prevent deadlocks
__CFRunLoopLock(rl);
潜在的死锁场景:
__weak typeof(self) weakSelf = self;
CFRunLoopPerformBlock(runLoop, mode, ^{
// block 捕获了 weakSelf
});
// 在对象的 dealloc 中
- (void)dealloc {
// 当 block 被释放时,weakSelf 也会被释放
// 如果开发者在 dealloc 中操作 RunLoop...
CFRunLoopRun(); // 尝试获取 RunLoop 锁 -> 死锁!
}
解决方案: 在解锁状态下释放 block,即使 dealloc 中操作 RunLoop 也不会死锁。
性能特性
1. 时间复杂度
-
遍历链表: O(n),n 为 blocks 数量
-
Mode 匹配: O(1) 或 O(m),m 为 modes 集合大小(通常很小)
-
链表操作: O(1)(插入/删除单个节点)
2. 空间复杂度
-
链表存储: O(n),n 为待处理的 blocks 数量
-
局部变量: O(1)
3. 性能优化
if (!rl->_blocks_head) return false; // 快速退出
如果没有 blocks,立即返回,避免不必要的操作。
使用场景
1. 在主线程异步执行代码
// 在后台线程
dispatch_async(backgroundQueue, ^{
// 执行耗时操作...
NSData *data = [self fetchDataFromNetwork];
// 切换到主线程更新 UI
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
self.imageView.image = [UIImage imageWithData:data];
});
CFRunLoopWakeUp(CFRunLoopGetMain()); // 唤醒主线程 RunLoop
});
2. 在特定 Mode 下执行代码
// 只在默认 mode 下执行(滚动时不执行)
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
[self performHeavyCalculation];
});
// 在所有 common modes 下执行(包括滚动时)
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
[self updateCriticalUI];
});
3. 跨线程通信
// 线程 A
CFRunLoopRef threadBRunLoop = ...; // 获取线程 B 的 RunLoop
CFRunLoopPerformBlock(threadBRunLoop, kCFRunLoopDefaultMode, ^{
NSLog(@"This runs on thread B");
});
CFRunLoopWakeUp(threadBRunLoop); // 唤醒线程 B
// 线程 B
CFRunLoopRun(); // 等待并处理事件(包括 blocks)
与 GCD 的对比
CFRunLoopPerformBlock vs dispatch_async
| 特性 |
CFRunLoopPerformBlock |
dispatch_async |
| 执行时机 |
在 RunLoop 循环中 |
在 GCD 队列中 |
| Mode 支持 |
✅ 可指定 mode |
❌ 无 mode 概念 |
| 优先级控制 |
❌ 按添加顺序 |
✅ 支持 QoS |
| 线程保证 |
✅ 绑定到特定 RunLoop |
❌ 线程由 GCD 管理 |
| 性能 |
较低(需要 RunLoop 循环) |
较高(GCD 优化) |
使用建议:
-
CFRunLoopPerformBlock: 需要与 RunLoop mode 交互(如 UI 更新)
-
dispatch_async: 通用异步任务(推荐)
与其他 RunLoop 函数的关系
__CFRunLoopRun (主循环)
│
├─ do {
│ │
│ ├─ __CFRunLoopDoObservers(kCFRunLoopBeforeTimers)
│ ├─ __CFRunLoopDoObservers(kCFRunLoopBeforeSources)
│ │
│ ├─ __CFRunLoopDoBlocks(rl, rlm) // ⭐ 处理 blocks
│ │
│ ├─ __CFRunLoopDoSources0(rl, rlm)
│ │
│ ├─ __CFRunLoopDoBlocks(rl, rlm) // ⭐ 再次处理(可能有新添加的)
│ │
│ ├─ 检查是否有 Source1 待处理
│ │
│ ├─ __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting)
│ ├─ __CFRunLoopServiceMachPort(...) // 等待事件
│ ├─ __CFRunLoopDoObservers(kCFRunLoopAfterWaiting)
│ │
│ ├─ 处理唤醒源(Timer/Source1/GCD)
│ │
│ └─ __CFRunLoopDoBlocks(rl, rlm) // ⭐ 最后再处理一次
│
└─ } while (!stop);
调用频率: 每次 RunLoop 循环通常调用 2-3 次。
潜在问题和注意事项
1. Block 执行顺序不保证
CFRunLoopPerformBlock(runLoop, mode, ^{ NSLog(@"1"); });
CFRunLoopPerformBlock(runLoop, mode, ^{ NSLog(@"2"); });
CFRunLoopPerformBlock(runLoop, mode, ^{ NSLog(@"3"); });
// 输出:可能是 1, 2, 3
// 但如果第一次循环某些 block 的 mode 不匹配,顺序可能改变
原因: 未执行的 blocks 会被重新插入队列头部。
2. Block 中的长时间操作
// ❌ 不好的做法
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
sleep(5); // 阻塞主线程 5 秒
// UI 会卡顿!
});
// ✅ 正确的做法
CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5); // 在后台线程执行
dispatch_async(dispatch_get_main_queue(), ^{
// 完成后更新 UI
});
});
});
3. Mode 不匹配导致 Block 不执行
// 添加到 Default mode
CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, ^{
NSLog(@"This will NOT run during scrolling");
});
CFRunLoopWakeUp(runLoop);
// 如果 RunLoop 当前在 UITrackingRunLoopMode(滚动时)
// Block 不会执行,会一直等到切换到 Default mode
解决方案: 使用 kCFRunLoopCommonModes:
CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{
NSLog(@"This runs in all common modes");
});
4. 忘记唤醒 RunLoop
// ❌ 不完整的代码
CFRunLoopPerformBlock(runLoop, mode, ^{
NSLog(@"This might not run immediately");
});
// 如果 RunLoop 正在休眠(等待事件),block 不会立即执行
// ✅ 正确的做法
CFRunLoopPerformBlock(runLoop, mode, ^{
NSLog(@"This will run soon");
});
CFRunLoopWakeUp(runLoop); // 唤醒 RunLoop
5. 循环引用
// ❌ 循环引用
self.runLoop = CFRunLoopGetCurrent();
CFRunLoopPerformBlock(self.runLoop, mode, ^{
[self doSomething]; // self 持有 runLoop,block 持有 self,runLoop 持有 block
});
// ✅ 使用 weak 引用
__weak typeof(self) weakSelf = self;
CFRunLoopPerformBlock(self.runLoop, mode, ^{
[weakSelf doSomething];
});
调试技巧
1. 查看待处理的 Blocks
// 在 LLDB 中
(lldb) p rl->_blocks_head
(lldb) p rl->_blocks_tail
// 遍历链表
(lldb) p ((struct _block_item *)rl->_blocks_head)->_next
2. 追踪 Block 执行
// 添加日志
CFRunLoopPerformBlock(runLoop, mode, ^{
NSLog(@"Block start: %@", [NSThread currentThread]);
// 业务代码...
NSLog(@"Block end");
});
3. 使用 Instruments
- 打开 Instruments
- 选择 "System Trace" 模板
- 查看
KDEBUG_EVENT_CFRL_IS_CALLING_BLOCK 事件
- 分析 block 执行耗时和频率
总结
__CFRunLoopDoBlocks 是 RunLoop 异步任务机制的核心实现,其精妙之处在于:
-
灵活的 Mode 匹配: 支持单 mode、多 mode、common modes
-
安全的锁管理: 执行前解锁,防止死锁和长时间持锁
-
高效的链表操作: 摘取-处理-放回的三段式设计
-
精细的内存管理: CFRetain/Block_copy 保证对象生命周期
-
智能的释放时机: 在重新加锁前释放 block,避免 dealloc 中的死锁
这个函数体现了 CoreFoundation 在性能、安全性和灵活性之间的精妙平衡,是理解 RunLoop 异步机制的关键。
扩展阅读
CFRunLoopPerformBlock 的实现
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void)) {
if (!rl || !block) return;
struct _block_item *item = malloc(sizeof(struct _block_item));
item->_next = NULL;
item->_mode = CFRetain(mode); // 持有 mode
item->_block = Block_copy(block); // 复制 block(栈 -> 堆)
__CFRunLoopLock(rl);
// 添加到链表尾部
if (!rl->_blocks_head) {
rl->_blocks_head = item;
} else {
rl->_blocks_tail->_next = item;
}
rl->_blocks_tail = item;
__CFRunLoopUnlock(rl);
}
Common Modes 的定义
// 在 Cocoa/UIKit 中
NSRunLoopCommonModes 包含:
- NSDefaultRunLoopMode (kCFRunLoopDefaultMode)
- UITrackingRunLoopMode
// 效果
CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, block);
// 等价于
CFRunLoopPerformBlock(runLoop, kCFRunLoopDefaultMode, block);
CFRunLoopPerformBlock(runLoop, UITrackingRunLoopMode, block);
这确保了 block 在 UI 滚动时也能执行,提升了响应性。