mcpField
S
swift-nio

swift-nio

github.com/wendylabsinc開発6281 回インストール5 週間前に更新
リポジトリ

swift-nio について

このスキルは、ネットワークアプリケーションやプロトコルの構築、Swift Concurrency との連携に取り組む開発者向けに SwiftNIO の指針を提供します。EventLoop の非ブロッキング、async/await、チャネル設計、ByteBuffer の効率的な利用を重視します。

主な機能

  1. 01EventLoop とチャネルの指針
  2. 02Swift Concurrency への移行パターン
  3. 03TCP・UDP サーバーとクライアントのパターン
  4. 04バイナリプロトコルのコーデック
  5. 05ByteBuffer のメモリとコピーの指針

活用シーン

  1. 01EventLoop のブロッキングを診断
  2. 02Future を async/await に移行
  3. 03バイナリプロトコルの解析を実装
  4. 04SwiftNIO で 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

概要

このスキルでは、Apple のイベント駆動型ネットワークアプリケーションフレームワークである SwiftNIO に関する専門的なガイダンスを提供します。開発者が安全で高性能なネットワークコードを記述し、プロトコルを実装し、Swift Concurrency と適切に統合できるよう支援します。

エージェントの動作規約(以下のルールに従ってください)

  1. プロジェクトの Package.swift を分析し、どの SwiftNIO パッケージが使用されているかを確認してください。
  2. 修正案を提示する前に、EventLoopFuture のチェーンの代わりに Swift Concurrency を使用できるか確認してください。
  3. EventLoop をブロックすることは決して推奨しないでください。これは SwiftNIO 開発で最も重要なルールです。
  4. 新しいコードでは、従来の ChannelHandler パターンよりも NIOAsyncChannel と構造化並行処理を優先してください。
  5. EventLoopFuture/EventLoopPromise は、低レベルのプロトコル実装でのみ使用してください。
  6. ByteBuffer を扱う際は、常にメモリの所有権を考慮し、不要なコピーを避けてください。

クイック判断ツリー

開発者が SwiftNIO に関するガイダンスを必要としている場合は、次の判断ツリーに従ってください。

  1. TCP/UDP サーバーまたはクライアントを構築する場合

    • Channel の概念と NIOAsyncChannel については references/Channels.md を読んでください
    • TCP サーバーには ServerBootstrap、UDP には DatagramBootstrap を使用してください
  2. EventLoop を理解する場合

    • イベントループの概念については references/EventLoops.md を読んでください
    • 重要:EventLoop をブロックしないでください!
  3. バイナリデータを扱う場合

    • バッファ操作については references/ByteBuffer.md を読んでください
    • 可能な場合は、コピーよりスライスビューを優先してください
  4. バイナリプロトコルを実装する場合

    • コーデックのパターンについては references/ByteToMessageCodecs.md を読んでください
    • ByteToMessageDecoder と MessageToByteEncoder を使用してください
  5. EventLoopFuture から async/await に移行する場合

    • フューチャーを async にブリッジするには .get() を使用してください
    • チャネルベースの async コードには NIOAsyncChannel を使用してください

トリアージ優先のプレイブック(よくある問題 → 解決策)

  • 「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 のメモリに関する問題

    • 可能な場合は readBytes ではなく readSlice を使用してください
    • ByteBuffer はコピーオンライト方式を採用していることに留意してください
    • references/ByteBuffer.md を参照してください
  • EventLoopFuture の待機によるデッドロック

    • 同じ EventLoop 内でフューチャーに対して .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、ChannelHandler、NIOAsyncChannel
  • ByteToMessageCodecs.md - バイナリプロトコルのシリアライズ/デシリアライズに使用する ByteToMessageDecoder、MessageToByteEncoder
  • patterns.md - 高度な統合パターン:ServerChildChannel 抽象化、ステートマシン、非コピー可能な ResponseWriter、グレースフルシャットダウン、ByteBuffer パターン

ベストプラクティスのまとめ

  1. EventLoopをブロックしないでください - 重い処理はスレッドプールにオフロードします
  2. 構造化並行処理を使用してください - レガシーハンドラーよりもNIOAsyncChannelを優先します
  3. シングルトンのEventLoopGroupを使用してください - MultiThreadedEventLoopGroup.singleton
  4. タスクグループでエラーを処理してください - クライアントタスクからエラーをスローすると、サーバーが閉じます
  5. パイプライン内の型に注意してください - 型の不一致は実行時にクラッシュを引き起こします
  6. ByteBufferを効率的に使用してください - コピーよりもスライスを優先します
バイナリプロトコルの処理にはByteBufferを使用する

バイナリデータを解析またはシリアライズする場合(特にネットワークプロトコルの場合)、FoundationのDataではなく、SwiftNIOのByteBufferを使用してください。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チャネルパターン

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. 内部にNIOストリームを持つカスタムのAsyncSequenceラッパー構造体を作成します
  2. ラッパーのAsyncIteratorでNIO型をパブリック型に変換します
  3. これにより、パブリックAPIで内部NIOのインポートを公開せずに済みます
SwiftNIO UDPに関する注意点
  • UDPソケットにはDatagramBootstrapを使用します
  • メッセージは、リモートアドレスとデータを含むAddressedEnvelope<ByteBuffer>を使用します
  • マルチキャストでは、チャネルをMulticastChannelプロトコルにキャストする必要があります
  • ソケットオプションにはSocketOptionValue(Int32)型を使用します
  • so_reuseportはLinuxでのみ利用できます

コメント