__CFRunLoopDoBlocks函数详解

11 阅读14分钟

借助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

前置条件

  • 函数调用时必须持有锁: rlrlm 都必须处于加锁状态
  • 函数返回时保持锁状态: 出口时 rlrlm 仍然加锁

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

匹配规则:

  1. 精确匹配:block.mode == currentMode
  2. 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);

匹配规则:

  1. 集合包含:currentMode ∈ block.modes
  2. 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

引用计数管理:

  1. CFRetain/CFRelease: 管理 mode 对象(CFString/CFSet)
  2. Block_copy/Block_release: 管理 block 对象
  3. 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

特性CFRunLoopPerformBlockdispatch_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 异步任务机制的核心实现,其精妙之处在于:

  1. 灵活的 Mode 匹配: 支持单 mode、多 mode、common modes
  2. 安全的锁管理: 执行前解锁,防止死锁和长时间持锁
  3. 高效的链表操作: 摘取-处理-放回的三段式设计
  4. 精细的内存管理: CFRetain/Block_copy 保证对象生命周期
  5. 智能的释放时机: 在重新加锁前释放 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 滚动时也能执行,提升了响应性。