Claude-skills swift-nio
Expert guidance on SwiftNIO best practices, patterns, and implementation. Use when developers mention: (1) SwiftNIO, NIO, ByteBuffer, Channel, ChannelPipeline, ChannelHandler, EventLoop, NIOAsyncChannel, or NIOFileSystem, (2) EventLoopFuture, ServerBootstrap, or DatagramBootstrap, (3) TCP/UDP server or client implementation, (4) ByteToMessageDecoder or wire protocol codecs, (5) binary protocol parsing or serialization, (6) blocking the event loop issues.
git clone https://github.com/wendylabsinc/claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/wendylabsinc/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/swift-nio" ~/.claude/skills/joannis-claude-skills-swift-nio && rm -rf "$T"
swift-nio/SKILL.mdSwift NIO
Overview
This skill provides expert guidance on SwiftNIO, Apple's event-driven network application framework. Use this skill to help developers write safe, performant networking code, build protocol implementations, and properly integrate with Swift Concurrency.
Agent Behavior Contract (Follow These Rules)
- Analyze the project's
to determine which SwiftNIO packages are used.Package.swift - Before proposing fixes, identify if Swift Concurrency can be used instead of
chains.EventLoopFuture - Never recommend blocking the EventLoop - this is the most critical rule in SwiftNIO development.
- Prefer
and structured concurrency over legacyNIOAsyncChannel
patterns for new code.ChannelHandler - Use
/EventLoopFuture
only for low-level protocol implementations.EventLoopPromise - When working with
, always consider memory ownership and avoid unnecessary copies.ByteBuffer
Quick Decision Tree
When a developer needs SwiftNIO guidance, follow this decision tree:
-
Building a TCP/UDP server or client?
- Read
for Channel concepts andreferences/Channels.mdNIOAsyncChannel - Use
for TCP servers,ServerBootstrap
for UDPDatagramBootstrap
- Read
-
Understanding EventLoops?
- Read
for event loop conceptsreferences/EventLoops.md - Critical: Never block the EventLoop!
- Read
-
Working with binary data?
- Read
for buffer operationsreferences/ByteBuffer.md - Prefer slice views over copies when possible
- Read
-
Implementing a binary protocol?
- Read
for codec patternsreferences/ByteToMessageCodecs.md - Use
andByteToMessageDecoderMessageToByteEncoder
- Read
-
Migrating from EventLoopFuture to async/await?
- Use
to bridge futures to async.get() - Use
for channel-based async codeNIOAsyncChannel
- Use
Triage-First Playbook (Common Issues -> Solutions)
-
"Blocking the EventLoop"
- Offload CPU-intensive work to a dispatch queue or use
NIOThreadPool - Never perform synchronous I/O on an EventLoop
- See
references/EventLoops.md
- Offload CPU-intensive work to a dispatch queue or use
-
Type mismatch crash in ChannelPipeline
- Ensure
of handler N matchesInboundOut
of handler N+1InboundIn - Ensure
of handler N matchesOutboundOut
of handler N-1OutboundIn - See
references/Channels.md
- Ensure
-
Implementing binary protocol serialization
- Use
for parsing bytes into messagesByteToMessageDecoder - Use
for serializing messages to bytesMessageToByteEncoder - Use
andreadLengthPrefixedSlice
helperswriteLengthPrefixed - See
references/ByteToMessageCodecs.md
- Use
-
Memory issues with ByteBuffer
- Use
instead ofreadSlice
when possiblereadBytes - Remember ByteBuffer uses copy-on-write semantics
- See
references/ByteBuffer.md
- Use
-
Deadlock when waiting for EventLoopFuture
- Never
on a future from within the same EventLoop.wait() - Use
from async contexts or chain with.get().flatMap
- Never
Core Patterns Reference
Creating a TCP Server (Modern Approach)
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 Best Practice
// Preferred: Use the singleton let group = MultiThreadedEventLoopGroup.singleton // Get any EventLoop from the group let eventLoop = group.any()
Bridging EventLoopFuture to async/await
// From EventLoopFuture to async let result = try await someFuture.get() // From async to EventLoopFuture let future = eventLoop.makeFutureWithTask { try await someAsyncOperation() }
ByteBuffer Operations
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)
Reference Files
Load these files as needed for specific topics:
- EventLoop concepts, nonblocking I/O, why blocking is badEventLoops.md
- Channel anatomy, ChannelPipeline, ChannelHandlers, NIOAsyncChannelChannels.md
- ByteToMessageDecoder, MessageToByteEncoder for binary protocol (de)serializationByteToMessageCodecs.md
- Advanced integration patterns: ServerChildChannel abstraction, state machines, noncopyable ResponseWriter, graceful shutdown, ByteBuffer patternspatterns.md
Best Practices Summary
- Never block the EventLoop - Offload heavy work to thread pools
- Use structured concurrency - Prefer
over legacy handlersNIOAsyncChannel - Use the singleton EventLoopGroup -
MultiThreadedEventLoopGroup.singleton - Handle errors in task groups - Throwing from a client task closes the server
- Mind the types in pipelines - Type mismatches crash at runtime
- Use ByteBuffer efficiently - Prefer slices over copies
Use ByteBuffer for Binary Protocol Handling
When parsing or serializing binary data (especially for network protocols), use SwiftNIO's
ByteBuffer instead of Foundation's Data. ByteBuffer provides:
- Efficient read/write operations with built-in endianness handling
- Zero-copy slicing with reader/writer index tracking
- Integration with NIO ecosystem
When converting between ByteBuffer and Data, use
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)
Bad - Using Data with manual byte manipulation:
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! } }
Good - Using 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) } } }
Binary Data Types Comparison
There are several "bag of bytes" data structures in Swift:
| Type | Source | Platform | Notes |
|---|---|---|---|
| stdlib | All | Safe, growable, good for Embedded Swift |
| stdlib (6.1+) | All | Fixed-size, stack-allocated, no heap allocation |
| Foundation | All (large binary) | Not always contiguous on Apple platforms |
| SwiftNIO | All (requires NIO) | Best for network protocols, not Embedded |
| stdlib (6.2+) | All | Zero-copy view, requires Swift 6.2+ |
| stdlib | All | Unsafe, manual memory management |
Recommendations:
- For iOS/macOS-only projects:
is fine due to framework integrationData - For SwiftNIO-based projects:
is required for I/O operationsByteBuffer - For Embedded Swift:
and[UInt8]InlineArray - For cross-platform APIs:
(Swift 6.2+) allows any backing typeSpan<UInt8>
NIO Channel Pattern with executeThenClose
Use
executeThenClose to get inbound/outbound streams from NIOAsyncChannel:
return try await channel.executeThenClose { inbound, outbound in let socket = Client(inbound: inbound, outbound: outbound, channel: channel.channel) return try await perform(client) }
Public API with Internal NIO Types
When exposing async sequences that wrap NIO types:
- Create a custom
wrapper struct with internal NIO streamAsyncSequence - The wrapper's
transforms NIO types to public typesAsyncIterator - This avoids exposing internal NIO imports in public API
SwiftNIO UDP Notes
- Use
for UDP socketsDatagramBootstrap - Messages use
containing remote address and dataAddressedEnvelope<ByteBuffer> - Multicast requires casting channel to
protocolMulticastChannel - Socket options use
(Int32) typeSocketOptionValue
is only available on Linuxso_reuseport