__CFRunLoopDoSource0函数详解(借助AI)

5 阅读6分钟

借助AI辅助。

函数概述

__CFRunLoopDoSource0 是 RunLoop 中负责执行单个 Source0 回调的核心函数。它检查 source 的状态,清除标记,并在确认有效后执行回调函数。这是处理 Source0 事件的最底层实现。

与上层函数的关系

__CFRunLoopDoSources0 (批量处理)
  ↓
  ├─ 收集所有待处理的 Source0
  ├─ 按优先级排序
  └─ 遍历调用 ──→ __CFRunLoopDoSource0 (单个处理) ⭐ 本函数
                     ↓
                   执行 perform 回调

函数签名

static Boolean __CFRunLoopDoSource0(CFRunLoopRef rl, CFRunLoopSourceRef rls);

参数说明

  • CFRunLoopRef rl: 当前运行的 RunLoop(用于自动释放池管理)
  • CFRunLoopSourceRef rls: 要处理的 Source0 对象

返回值

  • Boolean:
    • true: 成功执行了 source 的回调函数
    • false: source 未被处理(未标记为待处理、已无效等)

前置条件

  • 调用前不持有锁: RunLoop 和 RunLoopMode 应该已经解锁
  • Source 已 retain: 调用者需要保证 source 对象在调用期间不会被释放

完整代码逐行注释

static Boolean __CFRunLoopDoSource0(CFRunLoopRef rl, CFRunLoopSourceRef rls) {
    
    // 标志位:记录 source 是否被成功处理
    // true: 执行了回调函数
    // false: source 未被处理(未 signal、已 invalidate 等)
    Boolean sourceHandled = false;
    
    // 🔒 锁定 source 对象(细粒度锁)
    // 防止其他线程同时修改 source 的状态(如 signal、invalidate)
    // 注意:此时 RunLoop 和 RunLoopMode 已经解锁(在 __CFRunLoopDoSources0 中)
    __CFRunLoopSourceLock(rls);
    
    // 🔍 检查 source 是否被标记为待处理(signaled)
    // 标记方式:外部调用 CFRunLoopSourceSignal(rls) 会设置此标志位
    // 如果未被标记,说明:
    //   1. source 还没有事件需要处理,或
    //   2. 上次处理时已经清除了标记
    if (__CFRunLoopSourceIsSignaled(rls)) {
        // Source 被标记为待处理,需要处理
        
        // 🚩 清除待处理标记(unset signaled flag)
        // 重要:必须在执行回调之前清除,防止重复处理
        // 如果回调执行期间再次 signal,会重新设置标记,下次循环处理
        __CFRunLoopSourceUnsetSignaled(rls);
        
        // 🔍 再次检查 source 是否有效(valid)
        // ⚠️ 为什么需要二次检查?
        // 在 __CFRunLoopDoSources0 收集和现在执行之间,
        // 其他线程可能已经调用了 CFRunLoopSourceInvalidate(rls)
        // 
        // 时间线示例:
        //   t1: __CFRunLoopDoSources0 收集到 rls(此时有效)
        //   t2: 解锁 RunLoop 和 Mode
        //   t3: 其他线程调用 CFRunLoopSourceInvalidate(rls)
        //   t4: 本函数被调用,需要再次检查有效性
        if (__CFIsValid(rls)) {
            // Source 有效,可以安全执行回调
            
            // 🔓 解锁 source
            // 为什么在执行回调前解锁?
            // 1. 防止死锁:回调函数中可能操作 source(如再次 signal 或 invalidate)
            // 2. 提高并发:允许其他线程在回调执行期间访问 source(如检查状态)
            // 3. 避免长时间持锁:回调可能耗时很长
            __CFRunLoopSourceUnlock(rls);
            
            // ---------- 提取回调信息 ----------
            
            // 提取回调函数指针
            // perform 类型:void (*)(void *info)
            // version0 表示这是 Source0(非基于端口)
            void *perform = rls->_context.version0.perform;
            
            // 提取用户上下文信息(创建 source 时传入的)
            // info 可以是任何用户自定义数据的指针
            void *info = rls->_context.version0.info;
            
            // ---------- 自动释放池 ----------
            
            // 🔄 开始自动释放池(ARP = AutoRelease Pool)
            // 在 ObjC 运行时环境中,管理回调中创建的临时对象
            // 等价于:@autoreleasepool {
            CFRUNLOOP_ARP_BEGIN(rl)
            
            // ---------- 性能追踪 ----------
            
            // 📊 记录性能追踪点:开始调用 Source0 回调
            // 参数:事件类型、perform 函数指针、info、额外参数
            // 可用 Instruments 的 System Trace 查看
            cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE0 | DBG_FUNC_START, perform, info, 0, 0);
            
            // ---------- 🎯 核心回调执行 ----------
            
            // ⚡ 执行 Source0 的回调函数
            // 宏展开后通常是:
            //   if (perform) {
            //       ((void (*)(void *))perform)(info);
            //   }
            // 
            // 这是整个函数的核心目的!
            // 
            // ⚠️ 回调中可能发生的事情:
            // - 处理触摸事件(touchesBegan/Moved/Ended)
            // - 执行手势识别器的 action
            // - 处理自定义事件
            // - 更新 UI
            // - 再次操作 RunLoop(添加/移除 source、timer 等)
            // - 调用 CFRunLoopSourceSignal(重新标记自己或其他 source)
            // - 长时间阻塞(如果回调写得不好)
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(perform, info);
            
            // ---------- 性能追踪结束 ----------
            
            // 📊 记录性能追踪点:结束调用 Source0 回调
            // 可计算回调执行耗时 = end - start
            cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE0 | DBG_FUNC_END, perform, info, 0, 0);
            
            // ---------- 自动释放池结束 ----------
            
            // 🔄 结束自动释放池
            // 释放回调中创建的所有 autorelease 对象
            // 等价于:} // @autoreleasepool
            CFRUNLOOP_ARP_END()
            
            // ---------- Fork 检查 ----------
            
            // 🔒 检查进程是否被 fork
            // 如果在回调执行期间进程被 fork,需要重新初始化
            // 这个检查放在回调后很重要,因为:
            // 1. fork 可能发生在任何时候
            // 2. 子进程需要清理父进程的状态(锁、线程等)
            CHECK_FOR_FORK();
            
            // ✅ 标记:成功处理了 source
            sourceHandled = true;
            
        } else {
            // Source 已无效(被 invalidate)
            // 不执行回调,直接解锁并返回
            
            // 🔓 解锁 source
            __CFRunLoopSourceUnlock(rls);
            
            // sourceHandled 保持 false
        }
        
    } else {
        // Source 未被标记为待处理(not signaled)
        // 可能的原因:
        // 1. 从未调用过 CFRunLoopSourceSignal
        // 2. 上次处理时已经清除了标记,且之后没有再次 signal
        // 3. 其他线程在收集和执行之间清除了标记(少见)
        
        // 🔓 解锁 source
        __CFRunLoopSourceUnlock(rls);
        
        // sourceHandled 保持 false
    }
    
    // 返回是否成功处理了 source
    // true: 执行了回调函数
    // false: 未执行回调(未 signal 或已 invalidate)
    return sourceHandled;
}

函数执行流程图

                    __CFRunLoopDoSource0
                            ↓
                    🔒 Lock source
                            ↓
                   ┌────────┴────────┐
                   │  Is Signaled?   │
                   └────────┬────────┘
                            │
              ┌─────────────┴─────────────┐
              NO                          YES
              ↓                            ↓
          🔓 Unlock              🚩 Unset Signaled
              ↓                            ↓
         return false           ┌─────────┴─────────┐
                                │   Is Valid?       │
                                └─────────┬─────────┘
                                          │
                          ┌───────────────┴───────────────┐
                          NO                             YES
                          ↓                               ↓
                     🔓 Unlock                    🔓 Unlock
                          ↓                               ↓
                    return false              Extract perform & info
                                                          ↓
                                              🔄 AutoRelease Pool Begin
                                                          ↓
                                              ⚡ Execute perform(info)
                                                          ↓
                                              🔄 AutoRelease Pool End
                                                          ↓
                                              🔒 Check for fork
                                                          ↓
                                                   return true

使用示例

示例1:自定义事件处理

// 创建 Source0
- (CFRunLoopSourceRef)createCustomSource {
    CFRunLoopSourceContext context = {0};
    context.info = (__bridge void *)self;
    context.perform = customSourceCallback;
    
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(
        kCFAllocatorDefault, 
        0,  // order = 0(默认优先级)
        &context
    );
    
    return source;
}

// 回调函数
void customSourceCallback(void *info) {
    MyObject *obj = (__bridge MyObject *)info;
    [obj handleCustomEvent];
}

// 触发事件
- (void)postEvent {
    CFRunLoopSourceSignal(self.source);
    CFRunLoopWakeUp(CFRunLoopGetCurrent());
}

与相关函数的对比

__CFRunLoopDoSource0 vs __CFRunLoopDoSource1

特性__CFRunLoopDoSource0__CFRunLoopDoSource1
事件源类型Source0(非基于端口)Source1(基于 Mach Port)
触发方式手动 signal端口消息自动触发
状态检查IsSignaled + IsValid检查端口消息
回调类型void (*)(void *info)基于 Mach 消息
参数用户自定义 infoMach 消息内容
应用场景触摸事件、自定义事件IPC、系统事件

总结

__CFRunLoopDoSource0 是 Source0 处理机制的最底层实现,其设计精髓在于:

  1. 双重状态检查: IsSignaled + IsValid,确保安全性
  2. 精确的标记管理: 先清除标记,再执行回调,防止丢失事件
  3. 细粒度锁: 只在必要时持锁,回调前解锁
  4. 自动释放池: 管理回调中的临时对象
  5. Fork 感知: 在回调后检查 fork,保证子进程正确性

这个函数虽然简洁(约 30 行),但体现了 CoreFoundation 在性能、安全性和正确性之间的精妙平衡。理解它的实现对于:

  • 正确使用 Source0 API
  • 避免常见的陷阱(死锁、内存泄漏、竞态条件)
  • 优化事件处理性能
  • 实现高质量的自定义事件系统

都至关重要。