关于 swift-nio
此技能为构建网络应用、协议及 Swift 并发集成的开发者提供 SwiftNIO 指导。内容强调 EventLoop 非阻塞、async/await 模式、Channel 设计和高效使用 ByteBuffer。
主要功能
- 01EventLoop 与 Channel 指导
- 02Swift 并发迁移模式
- 03TCP 和 UDP 服务端或客户端模式
- 04二进制协议编解码
- 05ByteBuffer 内存与复制指导
适用场景
- 01排查 EventLoop 阻塞
- 02将 Future 迁移到 async/await
- 03实现二进制协议解析
- 04构建 SwiftNIO TCP 或 UDP 服务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill swift-nio安装后在 Claude Code 中输入/swift-nio确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/ByteToMessageCodecs.mdChannels.mdEventLoops.mdNIOAsyncChannel.mdpatterns.md详细介绍
Swift NIO
概述
此技能提供有关 SwiftNIO(Apple 的事件驱动型网络应用框架)的专业指导。使用此技能可帮助开发者编写安全、高性能的网络代码,构建协议实现,并妥善集成 Swift 并发。
Agent 行为约定(请遵循以下规则)
- 分析项目的
Package.swift,确定使用了哪些 SwiftNIO 包。 - 提出修复方案前,先判断能否使用 Swift 并发代替
EventLoopFuture链。 - 绝不要建议阻塞 EventLoop——这是 SwiftNIO 开发中最重要的规则。
- 编写新代码时,优先使用
NIOAsyncChannel和结构化并发,而不是旧式ChannelHandler模式。 - 仅在底层协议实现中使用
EventLoopFuture/EventLoopPromise。 - 处理
ByteBuffer时,始终考虑内存所有权,并避免不必要的复制。
快速决策树
开发者需要 SwiftNIO 方面的指导时,请遵循此决策树:
-
正在构建 TCP/UDP 服务端或客户端?
- 阅读
references/Channels.md,了解 Channel 概念和NIOAsyncChannel - TCP 服务端使用
ServerBootstrap,UDP 使用DatagramBootstrap
- 阅读
-
想了解 EventLoop?
- 阅读
references/EventLoops.md,了解事件循环概念 - 重要:绝不要阻塞 EventLoop!
- 阅读
-
正在处理二进制数据?
- 阅读
references/ByteBuffer.md,了解缓冲区操作 - 条件允许时,优先使用切片视图而不是复制
- 阅读
-
正在实现二进制协议?
- 阅读
references/ByteToMessageCodecs.md,了解编解码器模式 - 使用
ByteToMessageDecoder和MessageToByteEncoder
- 阅读
-
正在从 EventLoopFuture 迁移到 async/await?
- 使用
.get()将 future 桥接到 async - 使用
NIOAsyncChannel编写基于 channel 的异步代码
- 使用
优先分诊手册(常见问题 -> 解决方案)
-
“阻塞 EventLoop”
- 将 CPU 密集型工作转移到调度队列,或使用
NIOThreadPool - 绝不要在 EventLoop 上执行同步 I/O
- 请参阅
references/EventLoops.md
- 将 CPU 密集型工作转移到调度队列,或使用
-
ChannelPipeline 中发生类型不匹配崩溃
- 确保处理器 N 的
InboundOut与处理器 N+1 的InboundIn匹配 - 确保处理器 N 的
OutboundOut与处理器 N-1 的OutboundIn匹配 - 请参阅
references/Channels.md
- 确保处理器 N 的
-
正在实现二进制协议序列化
- 使用
ByteToMessageDecoder将字节解析为消息 - 使用
MessageToByteEncoder将消息序列化为字节 - 使用
readLengthPrefixedSlice和writeLengthPrefixed辅助方法 - 请参阅
references/ByteToMessageCodecs.md
- 使用
-
ByteBuffer 内存问题
- 条件允许时,使用
readSlice而不是readBytes - 请记住,ByteBuffer 使用写时复制语义
- 请参阅
references/ByteBuffer.md
- 条件允许时,使用
-
等待 EventLoopFuture 时发生死锁
- 绝不要在同一个 EventLoop 中对 future 调用
.wait() - 在 async 上下文中使用
.get(),或通过.flatMap链接
- 绝不要在同一个 EventLoop 中对 future 调用
核心模式参考
创建 TCP 服务端(现代方法)
let server = try await ServerBootstrap(group: MultiThreadedEventLoopGroup.singleton)
.bind(host: "0.0.0.0", port: 8080) { channel in
channel.eventLoop.makeCompletedFuture {
try NIOAsyncChannel(
wrappingChannelSynchronously: channel,
configuration: .init(
inboundType: ByteBuffer.self,
outboundType: ByteBuffer.self
)
)
}
}
try await withThrowingDiscardingTaskGroup { group in
try await server.executeThenClose { clients in
for try await client in clients {
group.addTask {
try await handleClient(client)
}
}
}
}
EventLoopGroup 最佳实践
// Preferred: Use the singleton
let group = MultiThreadedEventLoopGroup.singleton
// Get any EventLoop from the group
let eventLoop = group.any()
将 EventLoopFuture 桥接到 async/await
// From EventLoopFuture to async
let result = try await someFuture.get()
// From async to EventLoopFuture
let future = eventLoop.makeFutureWithTask {
try await someAsyncOperation()
}
ByteBuffer 操作
var buffer = ByteBufferAllocator().buffer(capacity: 1024)
// Writing
buffer.writeString("Hello")
buffer.writeInteger(UInt32(42))
// Reading
let string = buffer.readString(length: 5)
let number = buffer.readInteger(as: UInt32.self)
参考文件
根据具体主题按需加载以下文件:
EventLoops.md—— EventLoop 概念、非阻塞 I/O,以及阻塞为何有害Channels.md—— Channel 结构、ChannelPipeline、ChannelHandlers、NIOAsyncChannelByteToMessageCodecs.md—— 用于二进制协议(反)序列化的 ByteToMessageDecoder、MessageToByteEncoderpatterns.md—— 高级集成模式:ServerChildChannel 抽象、状态机、不可复制的 ResponseWriter、优雅关闭、ByteBuffer 模式
最佳实践摘要
- 切勿阻塞 EventLoop - 将繁重工作移交给线程池
- 使用结构化并发 - 优先使用
NIOAsyncChannel,而非旧式处理程序 - 使用单例 EventLoopGroup -
MultiThreadedEventLoopGroup.singleton - 在任务组中处理错误 - 从客户端任务中抛出错误会关闭服务
- 留意管道中的类型 - 类型不匹配会导致运行时崩溃
- 高效使用 ByteBuffer - 优先使用切片,而非副本
使用 ByteBuffer 处理二进制协议
解析或序列化二进制数据时(尤其是网络协议),请使用 SwiftNIO 的 ByteBuffer,而非 Foundation 的 Data。ByteBuffer 提供:
- 高效的读写操作,并内置字节序处理
- 通过读取器/写入器索引跟踪实现零拷贝切片
- 与 NIO 生态系统集成
在 ByteBuffer 和 Data 之间转换时,请使用 NIOFoundationCompat:
import NIOFoundationCompat
// ByteBuffer to Data
let data = Data(buffer: byteBuffer)
// Data to ByteBuffer - use writeData for better performance
var buffer = ByteBuffer()
buffer.writeData(data) // Faster than writeBytes(data)
不推荐 - 使用 Data 手动操作字节:
var buffer = Data()
var messageLength: UInt32?
for try await message in inbound {
buffer.append(message)
if messageLength == nil && buffer.count >= 4 {
messageLength = UInt32(buffer[0]) << 24
| UInt32(buffer[1]) << 16
| UInt32(buffer[2]) << 8
| UInt32(buffer[3])
buffer = Data(buffer.dropFirst(4)) // Copies data!
}
}
推荐 - 使用 ByteBuffer:
var buffer = ByteBuffer()
for try await message in inbound {
buffer.writeBytes(message)
if buffer.readableBytes >= 4 {
let readerIndex = buffer.readerIndex
guard let messageLength = buffer.readInteger(endianness: .big, as: UInt32.self) else {
continue
}
if buffer.readableBytes >= messageLength {
guard let bytes = buffer.readBytes(length: Int(messageLength)) else { continue }
// Process bytes...
} else {
// Not enough data yet, reset reader index
buffer.moveReaderIndex(to: readerIndex)
}
}
}
二进制数据类型比较
Swift 中有几种“字节包”数据结构:
| 类型 | 来源 | 平台 | 说明 |
|---|---|---|---|
Array<UInt8> | stdlib | 全部 | 安全、可增长,适用于 Embedded Swift |
InlineArray<N, UInt8> | stdlib (6.1+) | 全部 | 固定大小、栈分配,不进行堆分配 |
Data | Foundation | 全部(大型二进制数据) | 在 Apple 平台上不一定连续存储 |
ByteBuffer | SwiftNIO | 全部(需要 NIO) | 最适用于网络协议,不适用于 Embedded |
Span<UInt8> | stdlib (6.2+) | 全部 | 零拷贝视图,需要 Swift 6.2+ |
UnsafeBufferPointer<UInt8> | stdlib | 全部 | 不安全,需要手动管理内存 |
建议:
- 对于仅面向 iOS/macOS 的项目:由于框架集成,使用
Data即可 - 对于基于 SwiftNIO 的项目:I/O 操作需要使用
ByteBuffer - 对于 Embedded Swift:使用
[UInt8]和InlineArray - 对于跨平台 API:
Span<UInt8>(Swift 6.2+)支持任何底层存储类型
使用 executeThenClose 的 NIO Channel 模式
使用 executeThenClose 从 NIOAsyncChannel 获取入站/出站流:
return try await channel.executeThenClose { inbound, outbound in
let socket = Client(inbound: inbound, outbound: outbound, channel: channel.channel)
return try await perform(client)
}
使用内部 NIO 类型的公共 API
公开包装 NIO 类型的异步序列时:
- 创建一个自定义
AsyncSequence包装结构体,并在内部保存 NIO 流 - 包装器的
AsyncIterator将 NIO 类型转换为公共类型 - 这样可以避免在公共 API 中暴露内部 NIO 导入项
SwiftNIO UDP 说明
- 使用
DatagramBootstrap创建 UDP 套接字 - 消息使用
AddressedEnvelope<ByteBuffer>,其中包含远程地址和数据 - 多播需要将 channel 转型为
MulticastChannel协议 - 套接字选项使用
SocketOptionValue(Int32)类型 so_reuseport仅在 Linux 上可用