借助AI辅助。
__CFRunLoopDoSource1 函数详解
函数概述
__CFRunLoopDoSource1 是 RunLoop 中负责处理 Source1(基于 Mach Port 的事件源)的核心函数。与 Source0 不同,Source1 是基于内核端口的事件源,可以自动唤醒 RunLoop,常用于进程间通信(IPC)、系统事件处理等场景。
函数职责
- 检查 Source1 的有效性: 确认 source 未被 invalidate
- 清除信号标记: 重置 source 的待处理状态
- 执行回调函数: 调用用户提供的 perform 回调,传递 Mach 消息
- 返回处理状态: 告知 RunLoop 是否成功处理了 source
Source1 的特点
| 特性 | Source1 | Source0 |
|---|---|---|
| 基础 | 基于 Mach Port | 非基于端口 |
| 触发方式 | 内核端口消息自动触发 | 手动 signal + wakeup |
| 唤醒 RunLoop | 自动唤醒 | 需要手动唤醒 |
| 回调参数 | Mach 消息内容 | 用户自定义 info |
| 使用场景 | IPC、系统事件、端口通信 | 触摸事件、自定义事件 |
| 性能 | 涉及内核调用(较慢) | 用户态(较快) |
与上层的关系
__CFRunLoopRun (主循环)
↓
__CFRunLoopServiceMachPort(...)
↓ (收到 Mach 消息)
找到对应的 Source1
↓
__CFRunLoopDoSource1(rl, rlm, rls, msg, size, reply) ⭐ 本函数
↓
执行 perform 回调(处理 Mach 消息)
【完整代码逐行注释】
// msg, size and reply are unused on Windows
// 💡 Windows 平台说明:
// msg(消息)、size(大小)和 reply(回复)参数在 Windows 上未使用
// 因为 Windows 没有 Mach Port 机制,使用其他 IPC 机制
// ==================== 平台相关的函数签名 ====================
#if TARGET_OS_MAC
// 🍎 macOS/iOS 平台的函数声明
// 包含 Mach 消息相关的参数
static Boolean __CFRunLoopDoSource1(
CFRunLoopRef rl, // RunLoop 对象
CFRunLoopModeRef rlm, // RunLoop Mode
CFRunLoopSourceRef rls, // Source1 对象
mach_msg_header_t *msg, // Mach 消息头(输入)
CFIndex size, // 消息大小
mach_msg_header_t **reply // 回复消息(输出,可选)
) __attribute__((noinline));
// __attribute__((noinline)): 防止编译器内联优化
// 原因:便于调试、性能分析、保持调用栈可读性
// macOS/iOS 平台的函数实现
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls, mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply)
#else
// 🪟 Windows 平台的函数声明和实现
// 不需要 Mach 消息参数
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls)
#endif
{
// ==================== 初始化和 Fork 检查 ====================
// 🔒 检查进程是否被 fork
// 如果在 fork 后的子进程中,需要重新初始化 RunLoop 的锁和状态
// 防止继承父进程的锁状态导致死锁
CHECK_FOR_FORK();
// 标志位:记录 source 是否被成功处理
// true: 执行了回调函数
// false: source 无效或处理失败
Boolean sourceHandled = false;
/* Fire a version 1 source */
// 🔥 触发一个版本 1 的 source(基于 Mach Port)
// ==================== 准备阶段:增加引用计数 ====================
// 📌 增加 source 的引用计数
// 防止在执行期间 source 被其他线程释放
// 必须在最后配对 CFRelease(rls)
CFRetain(rls);
// ==================== 解锁阶段:准备执行回调 ====================
// 🔓 解锁 RunLoop Mode
__CFRunLoopModeUnlock(rlm);
// 🔓 解锁 RunLoop
__CFRunLoopUnlock(rl);
// ⚠️ 为什么要解锁?
// 1. 防止死锁:回调函数中可能调用 RunLoop API
// 2. 避免长时间持锁:source 的回调可能执行耗时操作
// 3. 提高并发性:允许其他线程在回调执行期间访问 RunLoop
// 🔒 锁定 source 对象(细粒度锁)
// 保护 source 的状态检查和修改
__CFRunLoopSourceLock(rls);
// ==================== 条件检查:验证 Source 有效性 ====================
// 🔍 检查 source 是否有效(未被 invalidate)
if (__CFIsValid(rls)) {
// Source 有效,可以执行回调
// 🚩 清除 source 的信号标记(signaled flag)
// Source1 虽然是基于端口的,但也有 signaled 标记
// 清除标记表示此事件已被处理
// 如果后续有新的端口消息到达,会重新设置标记
__CFRunLoopSourceUnsetSignaled(rls);
// 🔓 解锁 source
// 在执行回调前解锁,防止回调中操作 source 时死锁
__CFRunLoopSourceUnlock(rls);
// 📊 记录调试信息(如果启用了调试模式)
// 输出 source 的详细信息,用于问题诊断
__CFRunLoopDebugInfoForRunLoopSource(rls);
// ==================== 准备回调参数 ====================
// 提取回调函数指针
// version1.perform 的类型:
// void (*)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info)
// 或在 Windows 上:
// void (*)(void *info)
void *perform = rls->_context.version1.perform;
// 提取用户上下文信息(创建 source 时传入的)
void *info = rls->_context.version1.info;
// ==================== 执行回调函数 ====================
// 🔄 开始自动释放池(ARP = AutoRelease Pool)
// 管理回调中创建的临时对象
// 在 ObjC 运行时环境中,等价于 @autoreleasepool {
CFRUNLOOP_ARP_BEGIN(rl)
// 📊 记录性能追踪点:开始调用 Source1 回调
// 参数:事件类型、RunLoop、Mode、perform 函数指针、info
// 可用 Instruments 的 System Trace 查看
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE1 | DBG_FUNC_START, rl, rlm, perform, info);
// ⚡ 执行 Source1 的回调函数
// 这是整个函数的核心目的!
//
// macOS/iOS 平台的宏展开(简化):
// if (perform) {
// ((void (*)(void *, CFIndex, CFAllocatorRef, void *))perform)
// (msg, size, kCFAllocatorSystemDefault, info);
// }
//
// Windows 平台的宏展开(简化):
// if (perform) {
// ((void (*)(void *))perform)(info);
// }
//
// ⚠️ 回调中可能发生的事情:
// - 处理进程间通信(IPC)消息
// - 处理系统事件(如 CFMachPort、CFMessagePort)
// - 解析 Mach 消息内容
// - 构造并发送回复消息(通过 reply 参数)
// - 更新 UI(如果是主线程)
// - 再次操作 RunLoop(添加/移除 source、timer 等)
// - 长时间阻塞(如果回调写得不好)
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(perform,
#if TARGET_OS_MAC
// macOS/iOS: 传递 Mach 消息相关参数
msg, // 消息头指针
size, // 消息大小
reply, // 回复消息指针的指针(输出参数)
#endif
info // 用户上下文
);
// 📊 记录性能追踪点:结束调用 Source1 回调
// 可计算回调执行耗时 = end - start
cf_trace(KDEBUG_EVENT_CFRL_IS_CALLING_SOURCE1 | DBG_FUNC_END, rl, rlm, perform, info);
// 🔄 结束自动释放池
// 释放回调中创建的所有 autorelease 对象
// 等价于:} // @autoreleasepool
CFRUNLOOP_ARP_END()
// 🔒 再次检查进程是否被 fork
// 如果在回调执行期间进程被 fork,需要重新初始化
// 这个检查放在回调后很重要,因为 fork 可能发生在任何时候
CHECK_FOR_FORK();
// ✅ 标记:成功处理了 source
sourceHandled = true;
} else {
// Source 已无效(被 invalidate)
// 不执行回调,直接解锁并返回
// 📝 记录日志(如果启用了 RunLoop 日志)
// 输出:当前 RunLoop、程序名、无效的 source 指针
if (_LogCFRunLoop) {
CFLog(kCFLogLevelDebug,
CFSTR("%p (%s) __CFRunLoopDoSource1 rls %p is invalid"),
CFRunLoopGetCurrent(), // 当前 RunLoop
*_CFGetProgname(), // 程序名
rls); // source 指针
}
// 🔓 解锁 source
__CFRunLoopSourceUnlock(rls);
// sourceHandled 保持 false
}
// ==================== 清理阶段:释放引用计数并重新加锁 ====================
// 📉 释放 source 的引用计数
// 对应开头的 CFRetain(rls)
// 如果引用计数归零,source 对象会被销毁
CFRelease(rls);
// 🔒 重新锁定 RunLoop
__CFRunLoopLock(rl);
// 🔒 重新锁定 RunLoop Mode
__CFRunLoopModeLock(rlm);
// ✅ 恢复函数入口时的锁状态
// 满足调用约定:函数入口和出口时 rl 和 rlm 都处于加锁状态
// 返回是否成功处理了 source
// true: 执行了回调函数
// false: source 无效,未执行回调
return sourceHandled;
}
【函数执行流程图】
__CFRunLoopDoSource1
↓
🔒 CHECK_FOR_FORK
↓
📌 CFRetain(rls)
↓
🔓 Unlock mode & RunLoop
↓
🔒 Lock source
↓
┌───────────────────────────┐
│ Is Valid? │
└───────────┬───────────────┘
│
┌───────────┴───────────┐
NO YES
↓ ↓
📝 Log debug 🚩 Unset Signaled
↓ ↓
🔓 Unlock source 🔓 Unlock source
↓ ↓
📉 CFRelease 📊 Debug Info
↓ ↓
🔒 Lock rl & rlm 提取 perform & info
↓ ↓
return false 🔄 AutoRelease Begin
↓
📊 开始性能追踪
↓
┌───────────────────────────┐
│ ⚡ 执行回调函数 │
│ macOS: perform(msg, │
│ size, reply, info)│
│ Windows: perform(info) │
└───────────┬───────────────┘
↓
📊 结束性能追踪
↓
🔄 AutoRelease End
↓
🔒 CHECK_FOR_FORK
↓
sourceHandled = true
↓
📉 CFRelease(rls)
↓
🔒 Lock RunLoop & mode
↓
return true
【关键设计要点】
1. 跨平台差异处理
macOS/iOS 平台(Mach Port)
// 函数签名包含 Mach 消息参数
static Boolean __CFRunLoopDoSource1(
CFRunLoopRef rl,
CFRunLoopModeRef rlm,
CFRunLoopSourceRef rls,
mach_msg_header_t *msg, // Mach 消息头
CFIndex size, // 消息大小
mach_msg_header_t **reply // 回复消息
);
// 回调函数类型
typedef void (*CFRunLoopSourcePerform)(
void *msg, // 消息内容
CFIndex size, // 消息大小
CFAllocatorRef allocator, // 内存分配器
void *info // 用户上下文
);
Mach 消息结构:
typedef struct {
mach_msg_bits_t msgh_bits; // 标志位
mach_msg_size_t msgh_size; // 消息大小
mach_port_t msgh_remote_port; // 远程端口
mach_port_t msgh_local_port; // 本地端口
mach_msg_id_t msgh_id; // 消息 ID
// ... 消息体
} mach_msg_header_t;
Windows 平台
// 函数签名不包含消息参数
static Boolean __CFRunLoopDoSource1(
CFRunLoopRef rl,
CFRunLoopModeRef rlm,
CFRunLoopSourceRef rls
);
// 回调函数类型
typedef void (*CFRunLoopSourcePerform)(
void *info // 仅用户上下文
);
2. 锁的管理策略
入口状态:rl 锁定 + rlm 锁定
↓
解锁 rlm 和 rl
↓
锁定 source(细粒度锁)
↓
检查有效性并解锁 source
↓
执行回调(无全局锁)
↓
重新锁定 rl 和 rlm
↓
出口状态:rl 锁定 + rlm 锁定
为什么这样设计?
| 阶段 | 锁状态 | 原因 |
|---|---|---|
| 入口 | rl + rlm 加锁 | 保证调用时的状态一致性 |
| 检查前 | 只锁 source | 减少锁竞争,细粒度控制 |
| 回调时 | 无锁 | 防止死锁,允许回调中操作 RunLoop |
| 出口 | rl + rlm 加锁 | 恢复调用约定 |
3. Source1 的生命周期
创建: CFMachPortCreateRunLoopSource / CFMessagePortCreateRunLoopSource
↓
配置: 设置 Mach Port、perform 回调
↓
添加到 RunLoop: CFRunLoopAddSource(rl, source, mode)
↓
等待端口消息
↓
消息到达 → 自动唤醒 RunLoop
↓
__CFRunLoopServiceMachPort 接收消息
↓
__CFRunLoopDoSource1 处理
↓
├─ 执行 perform 回调
├─ 处理消息内容
└─ 可选:构造回复消息
↓
移除: CFRunLoopRemoveSource(rl, source, mode)
↓
销毁: CFRelease(source)
4. 与 Source0 的对比
| 特性 | Source0 | Source1 |
|---|---|---|
| 触发机制 | 手动 signal + wakeup | 端口消息自动触发 |
| 回调参数 | void *info | msg, size, reply, info |
| 状态标记 | IsSignaled | IsSignaled(也有) |
| 执行时机 | __CFRunLoopDoSources0 | __CFRunLoopDoSource1 |
| 使用复杂度 | 简单 | 较复杂(需要理解 Mach Port) |
| 性能 | 快(用户态) | 慢(内核态 IPC) |
| 可移植性 | 好(跨平台) | 差(依赖 Mach) |
5. 信号标记的作用
__CFRunLoopSourceUnsetSignaled(rls);
为什么 Source1 也需要 signaled 标记?
虽然 Source1 基于端口,但仍然使用 signaled 标记来:
- 标记待处理状态: 端口收到消息时设置
- 防止重复处理: 处理后清除标记
- 统一接口: 与 Source0 保持一致的状态管理
设置时机:
// 在 __CFRunLoopServiceMachPort 中
if (收到端口消息) {
__CFRunLoopSourceSetSignaled(source); // 设置标记
// 稍后调用 __CFRunLoopDoSource1
}
【使用场景】
1. CFMachPort(Mach 端口通信)
// 创建 Mach Port
CFMachPortContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
CFMachPortRef machPort = CFMachPortCreate(
kCFAllocatorDefault,
machPortCallback, // 回调函数
&context,
NULL
);
// 回调函数
void machPortCallback(CFMachPortRef port, void *msg, CFIndex size, void *info) {
mach_msg_header_t *header = (mach_msg_header_t *)msg;
// 解析 Mach 消息
NSLog(@"Received Mach message, ID: %d", header->msgh_id);
// 处理消息内容
// ...
}
// 创建 RunLoop Source
CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(
kCFAllocatorDefault,
machPort,
0 // order
);
// 添加到 RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 清理
CFRelease(source);
CFRelease(machPort);
2. CFMessagePort(高层消息通信)
// 创建本地 Message Port
CFMessagePortContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
CFMessagePortRef localPort = CFMessagePortCreateLocal(
kCFAllocatorDefault,
CFSTR("com.example.messageport"), // 端口名称
messagePortCallback, // 回调函数
&context,
NULL
);
// 回调函数
CFDataRef messagePortCallback(CFMessagePortRef port,
SInt32 msgid,
CFDataRef data,
void *info) {
// 处理接收到的数据
NSData *receivedData = (__bridge NSData *)data;
NSLog(@"Received message ID: %d, data: %@", msgid, receivedData);
// 返回回复数据(可选)
NSString *reply = @"Message received";
return (__bridge_retained CFDataRef)[reply dataUsingEncoding:NSUTF8StringEncoding];
}
// 创建 RunLoop Source
CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(
kCFAllocatorDefault,
localPort,
0
);
// 添加到 RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 从其他进程发送消息
CFMessagePortRef remotePort = CFMessagePortCreateRemote(
kCFAllocatorDefault,
CFSTR("com.example.messageport")
);
NSString *message = @"Hello";
CFDataRef data = (__bridge CFDataRef)[message dataUsingEncoding:NSUTF8StringEncoding];
SInt32 status = CFMessagePortSendRequest(
remotePort,
100, // 消息 ID
data,
10.0, // 发送超时
10.0, // 接收超时
NULL, // 回复 mode
NULL // 回复数据
);
3. 进程间通信(IPC)
// 进程 A:服务端
- (void)setupIPCServer {
// 创建 Mach Port
mach_port_t serverPort;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &serverPort);
// 注册端口(通过 Bootstrap Server)
// 这样其他进程可以查找到这个端口
// 创建 CFMachPort 包装
CFMachPortContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
CFMachPortRef cfPort = CFMachPortCreateWithPort(
kCFAllocatorDefault,
serverPort,
ipcCallback,
&context,
NULL
);
// 添加到 RunLoop
CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, cfPort, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopCommonModes);
}
void ipcCallback(CFMachPortRef port, void *msg, CFIndex size, void *info) {
mach_msg_header_t *header = (mach_msg_header_t *)msg;
// 处理 IPC 消息
NSLog(@"IPC message received from port: %d", header->msgh_remote_port);
}
// 进程 B:客户端
- (void)sendIPCMessage {
// 查找服务端的端口
mach_port_t serverPort = /* 从 Bootstrap Server 查找 */;
// 构造消息
mach_msg_header_t msg;
msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
msg.msgh_size = sizeof(msg);
msg.msgh_remote_port = serverPort;
msg.msgh_local_port = MACH_PORT_NULL;
msg.msgh_id = 100;
// 发送消息
mach_msg_send(&msg);
}
【总结】
__CFRunLoopDoSource1 是 RunLoop 处理基于 Mach Port 事件源的核心实现,其设计特点:
- 跨平台支持: 通过条件编译适配不同平台(macOS/iOS vs Windows)
- 简洁高效: 相比 Source0,逻辑更简单(不需要 signal/wakeup)
- 内核驱动: 依赖 Mach Port 的内核机制,自动唤醒 RunLoop
- 安全的锁管理: 回调前解锁,防止死锁
- 统一接口: 与 Source0 保持一致的状态管理(signaled 标记)
适用场景
- ✅ 进程间通信(IPC): 高效的跨进程消息传递
- ✅ 系统事件: 文件系统监控、网络事件等
- ✅ 底层服务: 需要与系统守护进程通信
- ❌ 高频事件: 不适合非常高频的事件(用 Source0)
与 Source0 的选择
| 场景 | 推荐 |
|---|---|
| 触摸事件、手势识别 | Source0 |
| 进程间通信 | Source1 |
| 自定义事件 | Source0 |
| 系统服务通信 | Source1 |
| 高频轮询 | Source0 |
理解 Source1 的实现对于掌握 macOS/iOS 的 IPC 机制、系统事件处理和 RunLoop 的完整工作流程至关重要。