Skip to content

SwiftNIO

Networking & ConcurrencyNetworking/AsyncSwift

What it is

SwiftNIO is Apple's event-driven network application framework, providing the non-blocking I/O foundation for high-performance Swift servers.

A channel pipeline of handlers processes inbound and outbound data. EventLoops are the threads, and work must never block them.

Installation

.package(url: "https://github.com/apple/swift-nio.git", from: "2.76.0")

Getting started

The smallest useful thing you can do with it, and what each part means.

A minimal server
final class EchoHandler: ChannelInboundHandler {
    typealias InboundIn = ByteBuffer
    typealias OutboundOut = ByteBuffer

    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        let buffer = unwrapInboundIn(data)
        context.write(wrapOutboundOut(buffer), promise: nil)
    }

    func channelReadComplete(context: ChannelHandlerContext) {
        context.flush()   // batch writes, then flush once
    }
}

let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer { try! group.syncShutdownGracefully() }

let channel = try ServerBootstrap(group: group)
    .serverChannelOption(ChannelOptions.backlog, value: 256)
    .childChannelInitializer { $0.pipeline.addHandler(EchoHandler()) }
    .bind(host: "0.0.0.0", port: 8080)
    .wait()

try channel.closeFuture.wait()
Writing without flushing and then flushing once in channelReadComplete is the standard NIO performance pattern — flushing per write costs a syscall each time.

Advanced usage

Where the library earns its place over a simpler alternative.

Never block the event loop
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
    let request = unwrapInboundIn(data)

    // Blocking here stalls every other connection on this event loop.
    threadPool.runIfActive(eventLoop: context.eventLoop) {
        try expensiveBlockingWork(request)
    }.whenComplete { result in
        // Back on the event loop — safe to touch the context.
        switch result {
        case .success(let response):
            context.writeAndFlush(self.wrapOutboundOut(response), promise: nil)
        case .failure:
            context.close(promise: nil)
        }
    }
}
One EventLoop serves many connections. A single blocking call there stalls all of them, which is the most common and most damaging mistake in NIO code.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Throughput collapses under concurrency
Something is blocking an event loop. Audit handlers for synchronous I/O, locks or heavy computation.
Precondition failure about the wrong event loop
A ChannelHandlerContext was touched from another thread. Hop back with eventLoop.execute before using it.

Best practices

  • Never block an EventLoop; offload to a NIOThreadPool and hop back.
  • Batch writes and flush once in channelReadComplete.
  • Use Vapor or Hummingbird unless you need protocol-level control.
  • Shut the EventLoopGroup down gracefully so connections close cleanly.

Background

Why it exists, and what it was reacting to.

Modelled on Netty, SwiftNIO is what made server-side Swift viable. Vapor, Hummingbird and Swift's gRPC implementation all build on it.