__CFRunLoopDoSources0函数详解

14 阅读9分钟

借助AI辅助。

__CFRunLoopDoSources0 函数逐行注释

函数概述

__CFRunLoopDoSources0 是 RunLoop 中负责处理 Source0 事件源的核心函数。Source0 是需要手动标记为待处理(signal)的事件源,常用于自定义事件处理、触摸事件、手势识别等场景。

Source0 与 Source1 的区别

Source0(非基于端口)

  • 触发方式: 需要手动调用 CFRunLoopSourceSignal() 标记为待处理,然后调用 CFRunLoopWakeUp() 唤醒 RunLoop
  • 特点: 不会自动唤醒 RunLoop,需要手动唤醒
  • 使用场景: 触摸事件、自定义事件、手势识别、UIEvent 处理
  • 实现: 基于回调函数

Source1(基于端口)

  • 触发方式: 基于 Mach Port,当端口收到消息时自动唤醒 RunLoop
  • 特点: 可以自动唤醒 RunLoop
  • 使用场景: 进程间通信、系统事件、CFMachPort、CFMessagePort
  • 实现: 基于 Mach 内核的端口通信

函数签名

/* rl is locked, rlm is locked on entrance and exit */
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));

参数说明

  • CFRunLoopRef rl: 当前运行的 RunLoop
  • CFRunLoopModeRef rlm: 当前的 RunLoop Mode
  • Boolean stopAfterHandle: 是否在处理一个 source 后就停止(用于优化性能)

返回值

  • Boolean: 如果至少处理了一个 source 返回 true,否则返回 false

前置条件

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

函数属性

  • __attribute__((noinline)): 防止编译器内联优化,便于调试和性能分析

完整代码逐行注释

/* rl is locked, rlm is locked on entrance and exit */
// 📝 锁状态约定:函数入口和出口时,rl 和 rlm 都必须处于加锁状态
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));

static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) {	/* DOES CALLOUT */
    // ⚠️ 重要标注:此函数会执行外部回调(callout),可能导致:
    // 1. 长时间阻塞(回调函数耗时)
    // 2. 重入问题(回调中可能再次操作 RunLoop)
    // 3. 死锁风险(因此需要在回调前解锁)
    
    // ==================== 第一部分:性能追踪和初始化 ====================
    
    // 📊 记录性能追踪点:开始处理 Source0
    // 参数:事件类型、RunLoop、Mode、stopAfterHandle 标志、额外参数
    // 可用 Instruments 的 kdebug 工具查看
    cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_SOURCES0 | DBG_FUNC_START, rl, rlm, stopAfterHandle, 0);
    
    // 🔒 检查进程是否被 fork
    // 如果在 fork 后的子进程中,需要重新初始化 RunLoop 的锁和状态
    // 防止继承父进程的锁状态导致死锁
    CHECK_FOR_FORK();
    
    // 用于存储收集到的 source(s)
    // 可能是单个 CFRunLoopSourceRef 或 CFArrayRef(多个 sources)
    CFTypeRef sources = NULL;
    
    // 标志位:是否至少处理了一个 source
    Boolean sourceHandled = false;
    
    // ==================== 第二部分:收集待处理的 Source0 ====================
    
    /* Fire the version 0 sources */
    // 🔥 触发版本 0 的 sources(Source0)
    
    // 检查当前 mode 是否有 Source0,并且数量大于 0
    // rlm->_sources0 是一个 CFSet,包含所有添加到此 mode 的 Source0
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) {
        
        // 📦 应用函数到 Set 中的每个元素
        // CFSetApplyFunction 会遍历 _sources0 集合,对每个 source 调用 __CFRunLoopCollectSources0
        // 参数说明:
        //   - rlm->_sources0: 要遍历的 CFSet
        //   - __CFRunLoopCollectSources0: 回调函数(收集器函数)
        //   - &sources: 上下文参数(传递给回调函数)
        // 
        // __CFRunLoopCollectSources0 的作用:
        //   - 检查每个 source 是否被标记为待处理(signaled)且有效
        //   - 如果只有一个待处理的 source,sources 被设置为该 source
        //   - 如果有多个待处理的 sources,sources 被设置为包含所有待处理 sources 的数组
        CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
    }
    
    // ==================== 第三部分:处理收集到的 Sources ====================
    
    // 如果收集到了待处理的 source(s)
    if (NULL != sources) {
        
        // 🔓 解锁 RunLoop Mode
        __CFRunLoopModeUnlock(rlm);
        
        // 🔓 解锁 RunLoop
        __CFRunLoopUnlock(rl);
        
        // ⚠️ 为什么要解锁?
        // 1. 防止死锁:source 的回调函数中可能调用 RunLoop API
        // 2. 避免长时间持锁:source 的回调可能执行耗时操作
        // 3. 提高并发性:允许其他线程在回调执行期间访问 RunLoop
        
        // 🛡️ 安全性保证:
        // - __CFRunLoopCollectSources0 已经对收集到的 sources 进行了 CFRetain
        // - 即使其他线程修改了 rlm->_sources0,也不会影响本次执行
        
        // sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef
        // sources 可能是单个(已持有)CFRunLoopSourceRef 或包含多个(已持有)CFRunLoopSourceRef 的数组
        
        // ---------- 情况1:单个 Source ----------
        
        // 判断 sources 是否是单个 CFRunLoopSource 对象
        // CFGetTypeID() 获取对象的类型ID
        if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) {
            
            // 类型转换为 CFRunLoopSourceRef
            CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
            
            // ⚡ 调用 __CFRunLoopDoSource0 处理单个 source
            // 这个函数会:
            // 1. 检查 source 是否有效且被标记为待处理
            // 2. 调用 source 的回调函数
            // 3. 清除 source 的待处理标记
            // 返回:是否成功处理了该 source
            sourceHandled = __CFRunLoopDoSource0(rl, rls);
            
        } else {
            // ---------- 情况2:多个 Sources(数组)----------
            
            // 获取数组中 source 的数量
            CFIndex cnt = CFArrayGetCount((CFArrayRef)sources);
            
            // 📊 对 sources 数组进行排序
            // 排序依据:source 的 order 字段(优先级)
            // order 值越小,优先级越高,越先执行
            // CFRangeMake(0, cnt) 表示对整个数组进行排序
            // __CFRunLoopSourceComparator 是比较函数
            CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL);
            
            // 遍历所有待处理的 sources
            for (CFIndex idx = 0; idx < cnt; idx++) {
                
                // 获取第 idx 个 source
                CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);
                
                // ⚡ 调用 __CFRunLoopDoSource0 处理当前 source
                // 返回:是否成功处理了该 source
                sourceHandled = __CFRunLoopDoSource0(rl, rls);
                
                // 🚪 提前退出优化
                // 如果 stopAfterHandle 为 true 且已经处理了一个 source
                // 则立即退出循环,不再处理剩余的 sources
                // 
                // 使用场景:
                // - 当 RunLoop 需要快速响应时(如处理用户输入)
                // - 避免一次处理太多 sources 导致 UI 卡顿
                // - 剩余的 sources 会在下次循环中继续处理
                if (stopAfterHandle && sourceHandled) {
                    break;
                }
            }
        }
        
        // 📉 释放 sources 对象
        // 减少引用计数(对应 __CFRunLoopCollectSources0 中的 CFRetain)
        // 如果引用计数归零,对象会被销毁
        CFRelease(sources);
        
        // 🔒 重新锁定 RunLoop
        __CFRunLoopLock(rl);
        
        // 🔒 重新锁定 RunLoop Mode
        __CFRunLoopModeLock(rlm);
        
        // ✅ 恢复函数入口时的锁状态
        // 满足函数签名中的约定:"rl is locked, rlm is locked on entrance and exit"
    }
    
    // ==================== 第四部分:结束性能追踪 ====================
    
    // 📊 记录性能追踪点:完成 Source0 处理
    cf_trace(KDEBUG_EVENT_CFRL_IS_DOING_SOURCES0 | DBG_FUNC_END, rl, rlm, stopAfterHandle, 0);
    
    // 返回是否至少处理了一个 source
    // true:处理了至少一个 source
    // false:没有处理任何 source(没有待处理的 sources 或所有 sources 都无效)
    return sourceHandled;
}

关键设计要点

1. Source0 的生命周期

创建: CFRunLoopSourceCreate
  ↓
添加到 RunLoop: CFRunLoopAddSource(rl, source, mode)
  ↓
标记为待处理: CFRunLoopSourceSignal(source)
  ↓
唤醒 RunLoop: CFRunLoopWakeUp(rl)
  ↓
RunLoop 循环中处理: __CFRunLoopDoSources0
  ↓
  ├─ 收集待处理的 sources (__CFRunLoopCollectSources0)
  ├─ 按优先级排序
  ├─ 执行回调 (__CFRunLoopDoSource0)
  └─ 清除待处理标记
  ↓
移除: CFRunLoopRemoveSource(rl, source, mode)
  ↓
销毁: CFRelease(source)

2. 锁的管理策略

入口状态:rl 锁定 + rlm 锁定
  ↓
收集 sources(持有锁)
  ↓
解锁 rl 和 rlm
  ↓
处理 sources(无全局锁,只锁定单个 source)
  ↓
重新锁定 rl 和 rlm
  ↓
出口状态:rl 锁定 + rlm 锁定

3. 优先级排序机制

// Source 的 order 字段决定执行顺序
CFRunLoopSourceRef source1 = CFRunLoopSourceCreate(...);
source1->_order = 100;  // 后执行

CFRunLoopSourceRef source2 = CFRunLoopSourceCreate(...);
source2->_order = 0;    // 先执行(默认值)

// 执行顺序:source2 -> source1

常见 order 值:

  • -2: 非常高优先级(系统级事件)
  • -1: 高优先级
  • 0: 默认优先级(大多数自定义 sources)
  • 1+: 低优先级

4. stopAfterHandle 优化

// 场景1:处理所有待处理的 sources
__CFRunLoopDoSources0(rl, rlm, false);  // 处理所有

// 场景2:只处理一个 source 就退出(快速响应)
__CFRunLoopDoSources0(rl, rlm, true);   // 处理一个就停止

使用场景:

stopAfterHandle = false(默认):
  - 正常的 RunLoop 循环
  - 希望一次性处理完所有待处理的事件
  
stopAfterHandle = true:
  - 需要快速响应新事件
  - 避免长时间阻塞(处理太多 sources)
  - 保持 UI 流畅性

使用场景

1. 处理触摸事件

iOS 的触摸事件系统使用 Source0:

// UIApplication 内部实现(简化版)
- (void)handleTouchEvent:(UIEvent *)event {
    // 1. 系统将触摸事件封装
    // 2. 创建 Source0 并标记为待处理
    CFRunLoopSourceSignal(touchEventSource);
    
    // 3. 唤醒主线程 RunLoop
    CFRunLoopWakeUp(CFRunLoopGetMain());
    
    // 4. RunLoop 循环中,__CFRunLoopDoSources0 被调用
    // 5. 触摸事件的回调被执行
    // 6. 事件传递给 UIView 的 touchesBegan/Moved/Ended
}

2. 自定义事件源

// 创建自定义 Source0
void performCallback(void *info) {
    NSLog(@"Custom source callback: %@", (__bridge id)info);
}

CFRunLoopSourceContext context = {0};
context.info = (__bridge void *)someObject;
context.perform = performCallback;

CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);

// 添加到 RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);

// 触发事件
CFRunLoopSourceSignal(source);
CFRunLoopWakeUp(CFRunLoopGetCurrent());

// 清理
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);

3. 手势识别

// UIGestureRecognizer 内部使用 Source0
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 分析触摸状态
    // 2. 标记手势识别器的 Source0 为待处理
    CFRunLoopSourceSignal(gestureSource);
    
    // 3. 在 RunLoop 中处理
    // 4. 调用手势回调(action)
}

4. 事件分发器

@interface EventDispatcher : NSObject
@property (nonatomic, strong) NSMutableArray *pendingEvents;
@property (nonatomic, assign) CFRunLoopSourceRef source;
@end

@implementation EventDispatcher

- (instancetype)init {
    self = [super init];
    if (self) {
        self.pendingEvents = [NSMutableArray array];
        
        // 创建 Source0
        CFRunLoopSourceContext context = {0};
        context.info = (__bridge void *)self;
        context.perform = dispatchEvents;
        
        self.source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        CFRunLoopAddSource(CFRunLoopGetMain(), self.source, kCFRunLoopCommonModes);
    }
    return self;
}

- (void)postEvent:(id)event {
    @synchronized(self.pendingEvents) {
        [self.pendingEvents addObject:event];
    }
    
    // 触发 Source0
    CFRunLoopSourceSignal(self.source);
    CFRunLoopWakeUp(CFRunLoopGetMain());
}

void dispatchEvents(void *info) {
    EventDispatcher *dispatcher = (__bridge EventDispatcher *)info;
    
    NSArray *events;
    @synchronized(dispatcher.pendingEvents) {
        events = [dispatcher.pendingEvents copy];
        [dispatcher.pendingEvents removeAllObjects];
    }
    
    for (id event in events) {
        // 处理事件
        NSLog(@"Dispatch event: %@", event);
    }
}

@end

实际应用案例

案例1:UIEvent 处理流程

// iOS 触摸事件处理(简化)
1. 用户触摸屏幕
   ↓
2. IOKit.framework 捕获硬件事件
   ↓
3. SpringBoard 接收事件
   ↓
4. 通过 IPC 发送到应用进程
   ↓
5. 应用的主线程创建 UIEvent6. 封装到 Source0 并 signal
   CFRunLoopSourceSignal(eventSource);
   CFRunLoopWakeUp(mainRunLoop);
   ↓
7. 主线程 RunLoop 被唤醒
   ↓
8. __CFRunLoopDoSources0 被调用
   ↓
9. 执行 event source 的回调
   ↓
10. UIApplication sendEvent:
    ↓
11. UIWindow sendEvent:
    ↓
12. 触摸事件传递到 UIView
    - touchesBegan:withEvent:
    - touchesMoved:withEvent:
    - touchesEnded:withEvent:

案例2:手势识别器

// UIGestureRecognizer 内部机制
1. UITouch 事件发生
   ↓
2. UIGestureRecognizer 接收 touches
   ↓
3. 更新状态机
   ↓
4. 如果手势被识别,标记 Source0
   CFRunLoopSourceSignal(gestureSource);
   ↓
5. RunLoop 处理 Source0
   ↓
6. 调用手势的 action
   [target performSelector:action withObject:gesture];

案例3:PerformSelector 的实现

// performSelector:withObject:afterDelay: 的简化实现
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay {
    if (delay == 0) {
        // 立即执行:使用 Source0
        PerformContext *context = [[PerformContext alloc] init];
        context.target = self;
        context.selector = aSelector;
        context.argument = anArgument;
        
        CFRunLoopSourceContext sourceContext = {0};
        sourceContext.info = (__bridge void *)context;
        sourceContext.perform = performSelectorCallback;
        
        CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
        
        CFRunLoopSourceSignal(source);
        CFRunLoopWakeUp(CFRunLoopGetCurrent());
        
        CFRelease(source);
    } else {
        // 延迟执行:使用 Timer
        [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:aSelector userInfo:anArgument repeats:NO];
    }
}

void performSelectorCallback(void *info) {
    PerformContext *context = (__bridge PerformContext *)info;
    [context.target performSelector:context.selector withObject:context.argument];
}

总结

__CFRunLoopDoSources0 是 RunLoop 中处理自定义事件的核心机制,其精妙之处在于:

  1. 灵活的优先级系统: 通过 order 字段实现细粒度的优先级控制
  2. 智能的收集器设计: 单个 source 避免数组创建,优化常见场景
  3. 安全的锁管理: 执行回调前解锁,防止死锁和长时间持锁
  4. 可控的执行策略: stopAfterHandle 参数平衡吞吐量和响应性
  5. 高效的排序机制: 只对待处理的 sources 排序,减少不必要的开销

Source0 是 iOS 事件处理系统的基础,理解它的工作原理对于:

  • 深入理解触摸事件传递机制
  • 实现高性能的自定义事件系统
  • 优化 RunLoop 性能
  • 避免常见的陷阱和错误

都至关重要。