Getting Started with SwiftNIO

Jonathan Wong
6 min readMay 16, 2018

You might have heard a few rumblings a while back about SwiftNIO (pronounced Swift Neo, like the guy from the Matrix ;-)). I was pretty excited when I first read about it and wanted to check it out and see how it differed from Netty in Java.

So far, if you are familiar with Netty, you should feel right at home with SwiftNIO. If you aren’t, keep reading.

Why Server-Side Swift?

Why not? If you are already an iOS developer and love writing Swift, why not try to build your backend in Swift? Compared to other languages like Java, C#, Ruby, Go and Node, Swift does not have nearly the same amount of packages available for you to reuse on the backend. However, all those communities had to start somewhere. If you look at newer languages like Go and Javascript on the backend, it’s people like you that are interested in the language and solving problems that help push the community forward.

Why SwiftNIO?

There are already some great server-side Swift frameworks like Vapor, Kitura and Perfect that you can get started in to build a web app. Vapor in fact uses SwiftNIO for their networking layer. What I want to do is introduce you to some lower level basics to better understand the higher level abstractions.

In this post, we are going to look at SwiftNIO. Taken from their Github page:

SwiftNIO is a cross-platform asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

It’s like Netty, but written for Swift.

SwiftNIO Architecture

First, let’s get a brief overview of SwiftNIO’s components.

A Channel is a construct that is responsible for handling inbound and outbound events. It connects to the underlying networking socket.

A Channel has a ChannelPipeline which is a doubly linked list of ChannelHandler objects.

The ChannelHandlers typically contain your application logic. ChannelHandler methods are triggered when networking events occur and are processed in order. In essence, the ChannelPipeline is a pipeline of data processing events.

Getting started

This tutorial is for Mac, but the steps for Linux should be quite similar. Since we are on the server, we are going to use Swift Package Manager (SPM). There are two parts:

  • create a TCP client
  • create a TCP server

Part 1: Create Client

We are going to run a few commands to create our project structure and generate our Xcode project. Open up your Terminal and run these commands:

$ mkdir TCPClient
$ cd TCPClient
$ swift package init --type executable
$ swift package generate-xcodeproj
$ open TCPClient.xcodeproj/

What this does is:

  • create a new directory for your project called TCPClient
  • change directory to TCPClient
  • have SPM create a command line application rather than a framework
  • have SPM generate an Xcode project for you
  • open the newly created TCPClient Xcode project

In your Package.swift file, we need to pull in SwiftNIO as a dependency. At the time of this article, we are pulling in version 1.5.1. Your Package.swift should look like:

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescriptionlet package = Package(
name: "TCPClient",
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", .exact("1.5.1"))
],
targets: [
.target(
name: "TCPClient",
dependencies: ["NIO"]),
]
)

Note: We are using an exact dependency for this article, but you alternatively can take advantage of SPM’s semantic versioning and use:

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

This way we depend on the minor version that we require and still get all the latest and greatest fixes and performance enhancements from the great team at Apple and other contributors to SwiftNIO!

Installing dependencies

After you add your dependencies, have SPM update your dependencies for your project and re-generate the Xcode project.

$ swift package update
$ swift package generate-xcodeproj

TCPClientHandler

The first class we are going to create is TCPClientHandler.

The channelActive method is the first method we override in our subclass of ChannelInboundHandler. This method creates a message of “SwiftNIO rocks!”. Within a ChannelPipeline, when a ChannelHandler is added, a ChannelHandlerContext is created to interact with the ChannelHandlers in the ChannelPipeline. A ChannelHandlerContext has a reference to the previous and next ChannelHandler. By using this ChannelHandlerContext, we get a reference to the Channel and use its ByteBufferAllocator to allocate a ByteBuffer, SwiftNIO’s container for bytes. We write our message to the buffer and call writeAndFlush on the ChannelHandlerContext. This enqueues the data to be written on the next flush event, which writes the data to the socket. We pass nil to the promise since we are not interested in getting notified when the data has been flushed.

The channelRead method unwraps the incoming data into a buffer where we unwrap the string and print it to the console. When there are no more bytes to be received, we close the channel.

The errorCaught method closes the channel if an error is received.

TCPClient

The second class we are going to create is TCPClient.

An EventLoop is an object that waits for incoming events (e.g. data received) and fires a callback when they do. An EventLoopGroup handles multiple EventLoops. Both EventLoop and EventLoopGroup are protocols and MultiThreadedEventLoopGroup is an implementation of EventLoopGroup. Because this is a simple client, we can use just one thread for our client.

The client also needs to connect to a host and port, so we set those in our initializer.

We have a variable called bootstrap that is of type ClientBootstrap. This is what we use to connect to the server in the start method.

Main

The last file is the main.swift. This is what actually starts our TCPClient.

Here we create an instance of our TCPClient, passing in the location of our server, localhost, and the port 3010 to connect to and run the start method. Next, let’s look at creating a server to connect to.

Part 2: Create Server

We are going to follow the same steps as in part 1 and create a new Xcode project for our server.

$ mkdir EchoServer
$ cd EchoServer
$ swift package init --type executable
$ swift package generate-xcodeproj
$ open EchoServer.xcodeproj/

Our Package.swift should look like:

// swift-tools-version:4.0// The swift-tools-version declares the minimum version of Swift required to build this package.import PackageDescriptionlet package = Package(
name: "EchoServer",
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git",
.exact("1.5.1"))
],
targets: [
.target(
name: "EchoServer",
dependencies: ["NIO"]),
]
)

Now you are ready to install your dependencies again.

$ swift package update
$ swift package generate-xcodeproj

Creating the server is very similar to creating the client. We are going to need a handler to handle the data and a way to bootstrap our server. Let’s start with handling our data first.

EchoHandler

The EchoHandler is going to echo what it receives back to the client. Like in the TCPClientHandler, we subclass ChannelInboundHandler.

In our channelRead method, we unwrap the incoming data into the buffer, read the amount of readable bytes, print that to the console, and write that data. When the read is complete, it flushes that data to the socket.

EchoServer

In our EchoServer, we again need a MultiThreadedEventLoopGroup, a host and a port.

Now because we are on the server, we give the MultiThreadedEventLoopGroup more cores. Also, SwiftNIO gives us ServerBootstrap to use on the server instead of using ClientBootstrap.

In the childChannelInitializer closure, we add an instance of our EchoHandler.

Main

Our main.swift for our server is very similar to our TCPClient main.swift. We create an instance of EchoServer on the host, localhost and port 3010 and run start.

Run it

Now that we have both a client and server, let’s give this a go. One of the nice things about Swift on the server is you can still use Xcode.

Server:

  • choose the EchoServer scheme to run on My Mac and hit Run
  • once you see a message in the Xcode console that you are connected on port 3010, run the client

Client:

  • choose the TCPClient scheme to run on My Mac and hit Run

Now you should see “SwiftNIO rocks!” in both the console of the server and client. The client connects to the server on port 3010 and sends the message “SwiftNIO rocks!”. The server reads those bytes, prints to its own console, and writes back out to the channel. The client then receives that message, prints to its own console, and closes the channel since there are no more bytes to read.

For the full projects:

Conclusion

Now you’ve created a client and server with SPM, SwiftNIO, and Xcode. We looked at some of the basic constructs of SwiftNIO and were able to send and receive a message through TCP. Next time we’ll look at another backend technology with Swift. Stay tuned!

--

--

Jonathan Wong

Cook, eat, run… San Diego Software Engineer, Pluralsight Author, RW Team Member