mcpField
S
swift-nio

swift-nio

github.com/wendylabsinc开发6281 次安装5 周前更新
代码仓库

关于 swift-nio

此技能为构建网络应用、协议及 Swift 并发集成的开发者提供 SwiftNIO 指导。内容强调 EventLoop 非阻塞、async/await 模式、Channel 设计和高效使用 ByteBuffer。

主要功能

  1. 01EventLoop 与 Channel 指导
  2. 02Swift 并发迁移模式
  3. 03TCP 和 UDP 服务端或客户端模式
  4. 04二进制协议编解码
  5. 05ByteBuffer 内存与复制指导

适用场景

  1. 01排查 EventLoop 阻塞
  2. 02将 Future 迁移到 async/await
  3. 03实现二进制协议解析
  4. 04构建 SwiftNIO TCP 或 UDP 服务

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wendylabsinc/claude-skills --skill swift-nio

安装后在 Claude Code 中输入/swift-nio确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ByteToMessageCodecs.md
Channels.md
EventLoops.md
NIOAsyncChannel.md
patterns.md

详细介绍

Swift NIO

概述

此技能提供有关 SwiftNIO(Apple 的事件驱动型网络应用框架)的专业指导。使用此技能可帮助开发者编写安全、高性能的网络代码,构建协议实现,并妥善集成 Swift 并发。

Agent 行为约定(请遵循以下规则)

  1. 分析项目的 Package.swift,确定使用了哪些 SwiftNIO 包。
  2. 提出修复方案前,先判断能否使用 Swift 并发代替 EventLoopFuture 链。
  3. 绝不要建议阻塞 EventLoop——这是 SwiftNIO 开发中最重要的规则。
  4. 编写新代码时,优先使用 NIOAsyncChannel 和结构化并发,而不是旧式 ChannelHandler 模式。
  5. 仅在底层协议实现中使用 EventLoopFuture/EventLoopPromise。
  6. 处理 ByteBuffer 时,始终考虑内存所有权,并避免不必要的复制。

快速决策树

开发者需要 SwiftNIO 方面的指导时,请遵循此决策树:

  1. 正在构建 TCP/UDP 服务端或客户端?

    • 阅读 references/Channels.md,了解 Channel 概念和 NIOAsyncChannel
    • TCP 服务端使用 ServerBootstrap,UDP 使用 DatagramBootstrap
  2. 想了解 EventLoop?

    • 阅读 references/EventLoops.md,了解事件循环概念
    • 重要:绝不要阻塞 EventLoop!
  3. 正在处理二进制数据?

    • 阅读 references/ByteBuffer.md,了解缓冲区操作
    • 条件允许时,优先使用切片视图而不是复制
  4. 正在实现二进制协议?

    • 阅读 references/ByteToMessageCodecs.md,了解编解码器模式
    • 使用 ByteToMessageDecoder 和 MessageToByteEncoder
  5. 正在从 EventLoopFuture 迁移到 async/await?

    • 使用 .get() 将 future 桥接到 async
    • 使用 NIOAsyncChannel 编写基于 channel 的异步代码

优先分诊手册(常见问题 -> 解决方案)

  • “阻塞 EventLoop”

    • 将 CPU 密集型工作转移到调度队列,或使用 NIOThreadPool
    • 绝不要在 EventLoop 上执行同步 I/O
    • 请参阅 references/EventLoops.md
  • ChannelPipeline 中发生类型不匹配崩溃

    • 确保处理器 N 的 InboundOut 与处理器 N+1 的 InboundIn 匹配
    • 确保处理器 N 的 OutboundOut 与处理器 N-1 的 OutboundIn 匹配
    • 请参阅 references/Channels.md
  • 正在实现二进制协议序列化

    • 使用 ByteToMessageDecoder 将字节解析为消息
    • 使用 MessageToByteEncoder 将消息序列化为字节
    • 使用 readLengthPrefixedSlice 和 writeLengthPrefixed 辅助方法
    • 请参阅 references/ByteToMessageCodecs.md
  • ByteBuffer 内存问题

    • 条件允许时,使用 readSlice 而不是 readBytes
    • 请记住,ByteBuffer 使用写时复制语义
    • 请参阅 references/ByteBuffer.md
  • 等待 EventLoopFuture 时发生死锁

    • 绝不要在同一个 EventLoop 中对 future 调用 .wait()
    • 在 async 上下文中使用 .get(),或通过 .flatMap 链接

核心模式参考

创建 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、NIOAsyncChannel
  • ByteToMessageCodecs.md —— 用于二进制协议(反)序列化的 ByteToMessageDecoder、MessageToByteEncoder
  • patterns.md —— 高级集成模式:ServerChildChannel 抽象、状态机、不可复制的 ResponseWriter、优雅关闭、ByteBuffer 模式

最佳实践摘要

  1. 切勿阻塞 EventLoop - 将繁重工作移交给线程池
  2. 使用结构化并发 - 优先使用 NIOAsyncChannel,而非旧式处理程序
  3. 使用单例 EventLoopGroup - MultiThreadedEventLoopGroup.singleton
  4. 在任务组中处理错误 - 从客户端任务中抛出错误会关闭服务
  5. 留意管道中的类型 - 类型不匹配会导致运行时崩溃
  6. 高效使用 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+)全部固定大小、栈分配,不进行堆分配
DataFoundation全部(大型二进制数据)在 Apple 平台上不一定连续存储
ByteBufferSwiftNIO全部(需要 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 类型的异步序列时:

  1. 创建一个自定义 AsyncSequence 包装结构体,并在内部保存 NIO 流
  2. 包装器的 AsyncIterator 将 NIO 类型转换为公共类型
  3. 这样可以避免在公共 API 中暴露内部 NIO 导入项
SwiftNIO UDP 说明
  • 使用 DatagramBootstrap 创建 UDP 套接字
  • 消息使用 AddressedEnvelope<ByteBuffer>,其中包含远程地址和数据
  • 多播需要将 channel 转型为 MulticastChannel 协议
  • 套接字选项使用 SocketOptionValue(Int32)类型
  • so_reuseport 仅在 Linux 上可用

评论

支持 Markdown