swift-nio について
このスキルは、ネットワークアプリケーションやプロトコルの構築、Swift Concurrency との連携に取り組む開発者向けに SwiftNIO の指針を提供します。EventLoop の非ブロッキング、async/await、チャネル設計、ByteBuffer の効率的な利用を重視します。
主な機能
- 01EventLoop とチャネルの指針
- 02Swift Concurrency への移行パターン
- 03TCP・UDP サーバーとクライアントのパターン
- 04バイナリプロトコルのコーデック
- 05ByteBuffer のメモリとコピーの指針
活用シーン
- 01EventLoop のブロッキングを診断
- 02Future を async/await に移行
- 03バイナリプロトコルの解析を実装
- 04SwiftNIO で 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
概要
このスキルでは、Apple のイベント駆動型ネットワークアプリケーションフレームワークである SwiftNIO に関する専門的なガイダンスを提供します。開発者が安全で高性能なネットワークコードを記述し、プロトコルを実装し、Swift Concurrency と適切に統合できるよう支援します。
エージェントの動作規約(以下のルールに従ってください)
- プロジェクトの
Package.swiftを分析し、どの SwiftNIO パッケージが使用されているかを確認してください。 - 修正案を提示する前に、
EventLoopFutureのチェーンの代わりに Swift Concurrency を使用できるか確認してください。 - EventLoop をブロックすることは決して推奨しないでください。これは SwiftNIO 開発で最も重要なルールです。
- 新しいコードでは、従来の
ChannelHandlerパターンよりもNIOAsyncChannelと構造化並行処理を優先してください。 EventLoopFuture/EventLoopPromiseは、低レベルのプロトコル実装でのみ使用してください。ByteBufferを扱う際は、常にメモリの所有権を考慮し、不要なコピーを避けてください。
クイック判断ツリー
開発者が SwiftNIO に関するガイダンスを必要としている場合は、次の判断ツリーに従ってください。
-
TCP/UDP サーバーまたはクライアントを構築する場合
- Channel の概念と
NIOAsyncChannelについてはreferences/Channels.mdを読んでください - TCP サーバーには
ServerBootstrap、UDP にはDatagramBootstrapを使用してください
- Channel の概念と
-
EventLoop を理解する場合
- イベントループの概念については
references/EventLoops.mdを読んでください - 重要:EventLoop をブロックしないでください!
- イベントループの概念については
-
バイナリデータを扱う場合
- バッファ操作については
references/ByteBuffer.mdを読んでください - 可能な場合は、コピーよりスライスビューを優先してください
- バッファ操作については
-
バイナリプロトコルを実装する場合
- コーデックのパターンについては
references/ByteToMessageCodecs.mdを読んでください ByteToMessageDecoderとMessageToByteEncoderを使用してください
- コーデックのパターンについては
-
EventLoopFuture から async/await に移行する場合
- フューチャーを async にブリッジするには
.get()を使用してください - チャネルベースの async コードには
NIOAsyncChannelを使用してください
- フューチャーを async にブリッジするには
トリアージ優先のプレイブック(よくある問題 → 解決策)
-
「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 のメモリに関する問題
- 可能な場合は
readBytesではなくreadSliceを使用してください - ByteBuffer はコピーオンライト方式を採用していることに留意してください
references/ByteBuffer.mdを参照してください
- 可能な場合は
-
EventLoopFuture の待機によるデッドロック
- 同じ EventLoop 内でフューチャーに対して
.wait()を呼び出さないでください - async コンテキストからは
.get()を使用するか、.flatMapでチェーンしてください
- 同じ EventLoop 内でフューチャーに対して
コアパターンのリファレンス
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、NIOAsyncChannelByteToMessageCodecs.md- バイナリプロトコルのシリアライズ/デシリアライズに使用する ByteToMessageDecoder、MessageToByteEncoderpatterns.md- 高度な統合パターン:ServerChildChannel 抽象化、ステートマシン、非コピー可能な ResponseWriter、グレースフルシャットダウン、ByteBuffer パターン
ベストプラクティスのまとめ
- EventLoopをブロックしないでください - 重い処理はスレッドプールにオフロードします
- 構造化並行処理を使用してください - レガシーハンドラーよりも
NIOAsyncChannelを優先します - シングルトンのEventLoopGroupを使用してください -
MultiThreadedEventLoopGroup.singleton - タスクグループでエラーを処理してください - クライアントタスクからエラーをスローすると、サーバーが閉じます
- パイプライン内の型に注意してください - 型の不一致は実行時にクラッシュを引き起こします
- 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+) | すべて | 固定サイズでスタックに割り当てられ、ヒープ割り当てがありません |
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チャネルパターン
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型をラップする非同期シーケンスを公開する場合:
- 内部にNIOストリームを持つカスタムの
AsyncSequenceラッパー構造体を作成します - ラッパーの
AsyncIteratorでNIO型をパブリック型に変換します - これにより、パブリックAPIで内部NIOのインポートを公開せずに済みます
SwiftNIO UDPに関する注意点
- UDPソケットには
DatagramBootstrapを使用します - メッセージは、リモートアドレスとデータを含む
AddressedEnvelope<ByteBuffer>を使用します - マルチキャストでは、チャネルを
MulticastChannelプロトコルにキャストする必要があります - ソケットオプションには
SocketOptionValue(Int32)型を使用します so_reuseportはLinuxでのみ利用できます