- Event
- swift-event vs SwiftNIO vs Hummingbird vs Network.framework
Article
swift-event vs SwiftNIO vs Hummingbird vs Network.framework
A decision-tree comparison of the four common Swift networking choices, written from inside swift-event but trying not to mark its own homework.
Overview
Four options keep coming up in “what should I use for networking in Swift?” forum threads:
Event — this package. Thin async/await wrapper (Socket, ServerSocket, EventLoop) over libevent’s kqueue/epoll multiplexer. Plain TCP, no built-in TLS/HTTP/WebSocket.
SwiftNIO — Apple’s full event-loop networking stack. Channel pipelines, protocol handlers, an ecosystem of modules (HTTP/1, HTTP/2, WebSocket, TLS).
Hummingbird — server-side Swift HTTP framework built on SwiftNIO. Routing, middleware, async/await-native APIs above NIO’s channel layer.
Network.framework — Apple’s modern (2018) networking framework, callback-shaped, deeply integrated with the system network stack on Apple platforms only.
This article is a decision tree, not a tier list. Each is the right answer for a specific shape of problem.
Decision tree
Pick the framework by question shape, not feature checklist: HTTP server → Hummingbird; Apple-only with TLS → Network.framework; plain TCP with async/await → Event; 10k+ concurrent connections → SwiftNIO; embedding a libevent-based C library → Event’s libevent product. The five-question tree below disambiguates the edges.
Are you building an HTTP server (REST, GraphQL, WebSocket)?
Are you Apple-platform-only and need TLS, multipath, or system-managed routing?
Are you doing plain TCP and you want async/await with a small dependency footprint?
Do you need extreme connection scale (10k+ concurrent connections, multi-core saturation)?
Are you embedding a C library that already speaks libevent (Tor, libssh, custom C code)?
Note
This decision tree shortcuts when the answer to question 1 is “yes” (HTTP server). Hummingbird and SwiftNIO + swift-nio-http are the only practical Swift choices for production HTTP servers — Event does not wrap HTTP and we have no plan to. Question 3 onward is for non-HTTP cases.
Side-by-side comparison table
| Capability |
Event |
SwiftNIO |
Hummingbird |
Network.framework |
| Async/await native |
✅ |
Partial (NIOAsyncChannel bridges) |
✅ |
❌ (callback-only) |
| Plain TCP client/server |
✅ |
✅ |
(via NIO) |
✅ |
| HTTP/1 server |
❌ |
✅ (swift-nio-http1) |
✅ |
❌ |
| HTTP/2 server |
❌ |
✅ (swift-nio-http2) |
✅ |
❌ |
| WebSocket |
❌ |
✅ (swift-nio-websocket) |
✅ |
Partial (NWWebSocket) |
| TLS |
❌ |
✅ (swift-nio-ssl) |
✅ |
✅ (built-in) |
| UDP |
❌ |
✅ |
(server-focused) |
✅ |
| Unix-domain sockets |
❌ |
✅ |
(via NIO) |
❌ |
| Linux support |
✅ |
✅ |
✅ |
❌ (Apple-only) |
| iOS support |
✅ |
✅ |
✅ (NIOTransportServices) |
✅ |
| Threading model |
Single loop, single owner per scope |
MultiThreadedEventLoopGroup (configurable) |
NIO’s ELG |
System-managed |
| Backpressure |
Manual (planned) |
Built-in via channel pipeline |
Built-in via NIO |
Built-in via NWConnection state |
| Direct C-library embedding |
✅ (libevent product) |
❌ |
❌ |
❌ |
| Cancellation propagation |
Partial (see Production Considerations) |
Partial (NIOAsyncChannel known issues) |
✅ via async/await |
N/A (callback model) |
| Pre-1.0 status |
✅ (0.x today) |
❌ (stable, 2.x) |
❌ (stable, 2.x) |
❌ (stable since iOS 12) |
| Approximate code size to “echo server” |
~10 lines |
~50 lines (channel pipeline) |
~10 lines |
~30 lines (callback bridging) |
Code shape comparison: an echo server in each
The same problem — accept TCP connections and echo back received bytes — takes ~10 lines in Event, ~50 lines in SwiftNIO, ~10 lines in Hummingbird (HTTP, not raw TCP), and ~30 lines in Network.framework. The line counts reflect the abstraction depth each library imposes, not the underlying capability.
Event — full compiled snippet using listen(port:backlog:loop:), connections, read(maxBytes:timeout:), and write(_:timeout:):
SwiftNIO (using NIOAsyncChannel, the post-2024 ergonomic shape):
let server = try await ServerBootstrap(group: NIOSingletons.posixEventLoopGroup)
.childChannelOption(.socketOption(.so_reuseaddr), value: 1)
.bind(host: "0.0.0.0", port: 8080) { channel in
channel.eventLoop.makeCompletedFuture {
try NIOAsyncChannel<ByteBuffer, ByteBuffer>(wrappingChannelSynchronously: channel)
}
}
try await withThrowingDiscardingTaskGroup { group in
try await server.executeThenClose { connections in
for try await connection in connections {
group.addTask {
try await connection.executeThenClose { inbound, outbound in
for try await chunk in inbound {
try await outbound.write(chunk)
}
}
}
}
}
}
Hummingbird (HTTP, not raw TCP — Hummingbird’s domain is HTTP so the equivalent is “echo POST body”):
let router = Router()
router.post("/") { request, _ in
let body = try await request.body.collect(upTo: .max)
return Response(status: .ok, body: ResponseBody(byteBuffer: body))
}
let app = Application(router: router, configuration: .init(address: .hostname("0.0.0.0", port: 8080)))
try await app.runService()
Network.framework:
let listener = try NWListener(using: .tcp, on: 8080)
listener.newConnectionHandler = { connection in
connection.start(queue: .global())
connection.receive(minimumIncompleteLength: 1, maximumLength: 4096) { data, _, _, _ in
guard let data = data else { return }
connection.send(content: data, completion: .contentProcessed { _ in
connection.cancel()
})
}
}
listener.start(queue: .global())
RunLoop.main.run()
Choosing for specific scenarios
“I’m writing an iOS app that needs to talk plain TCP to my server.” → Event via connect(to:port:loop:timeout:). See Async TCP Client for iOS in Swift. Network.framework works too but the callback shape is awkward in modern Swift.
“I’m writing a Vapor-style web service.” → Hummingbird (or Vapor itself if you want the full ORM/template ecosystem). Both build on NIO under the hood.
“I need to handle 50k concurrent WebSocket clients.” → SwiftNIO directly, with MultiThreadedEventLoopGroup sized to your core count, plus swift-nio-websocket.
“I’m porting a C codebase that uses libevent (Tor, libssh, custom binary protocol library) to a Swift package.” → Event. The package’s libevent C product is consumable by other Swift packages exactly for this case. See Choosing Between Event and libevent and Embedding a C Library in Swift Package Manager.
“I need TLS but don’t want to depend on swift-nio-ssl.” → Network.framework on Apple platforms (TLS is built in). On Linux, you currently have to bring TLS yourself; Event does not wrap TLS today.
“I want to write an MQTT broker / Redis-compatible server / custom binary-protocol daemon.” → Almost certainly Event is enough, especially if the protocol is length-prefixed binary (see Handling Partial Reads in Swift TCP Sockets: Length-Prefix and Delimiter Framing). Reach for SwiftNIO if the protocol is async-message-multiplexed in ways that benefit from a channel-pipeline architecture.
Honest disclosures
Important
This article is published in Event’s own documentation. SwiftNIO and Hummingbird are mature, production-grade libraries with years of real deployment behind them; Event is pre-1.0 and ships a deliberately narrow surface. The decision tree above tries to recommend the right tool for the job rather than the home team — but you should weigh the source.
Two biases worth naming explicitly:
Recency bias. Event is pre-1.0; SwiftNIO and Hummingbird are mature stable libraries with years of production use. The capability gaps in the comparison table are not abstract — they reflect features Event has deliberately not yet shipped, and “we don’t have your bug” can become “we don’t have your feature” quickly. See Production Considerations for the full caveats list (and SocketError for the error surface).
Scope bias. Comparing a thin libevent wrapper (Socket / ServerSocket / EventLoop) to a full HTTP framework (Hummingbird) is comparing different layers. The decision tree above tries to surface this — the right comparison for “what HTTP server should I use” is Hummingbird vs Vapor, not Hummingbird vs Event.
For the libevent vs Event choice specifically (when to drop down from the Swift API to the raw C product), see Choosing Between Event and libevent.
See Also
Related Documentation
Choosing Between Event and libevent
This package ships two library products — which one should you link? This article covers the decision boundary, the canonical consumer example (swift-tor), and the stability guarantees that differ between the idiomatic Swift API and the raw C bindings.
Async TCP Client for iOS in Swift
A working TCP client that compiles for iOS (and iPadOS, tvOS, visionOS), with the simulator-localhost gotchas explicit and the NWConnection callback dance avoided.
How to Write an Async TCP Server in Swift
A complete async TCP server in Swift 6, end-to-end — bind, listen, accept, per-connection handler tasks, graceful close — without channel pipelines or EventLoopFuture plumbing.
Embedding a C Library in Swift Package Manager
How to make a C library consumable from Swift Package Manager — using libevent and Event as the worked example, with the module-map and product-re-export patterns extracted for your own ports.
Production Considerations
Pre-1.0 status, the concurrency model, resource-ownership rules, and the list of capabilities not yet shipping in Event.
Async TCP sockets and event-loop primitives for Swift on top of kqueue (Apple platforms) or epoll (Linux), backed by a vendored build of libevent.
class EventLoopA libevent-backed event loop owning an event_base for async I/O dispatch.
class SocketAn async non-blocking TCP socket backed by libevent.
class ServerSocketA listening TCP server socket built on a non-blocking file descriptor and an EventLoop.
enum SocketErrorAn error surfaced by SocketAddress, Socket, and ServerSocket operations.