SwiftNIO: A simple guide to async on the server

An important topic in server-side Swift is asynchronous programming. This tutorial teaches you how to work with two important aspects of async programming: futures and promises, using SwiftNIO. By Joannis Orlandos.

Leave a rating/review
Download materials
Save for later
Share

An important topic in server-side Swift and SwiftNIO is asynchronous programming. Asynchronous programming is a way of programming that enables the program to work on multiple tasks in parallel by switching to a different task when waiting for an external factor. It allows large numbers of users to access your website at the same time and keeps the back end running at 88 miles per hour.

Two key aspects of asynchronous programming are futures and promises. Using a website that keeps track of quotes, this tutorial will teach you how to work with futures and promises. So let’s begin, before the event loop runs away!

For this tutorial, you will need Xcode 10 or later, and you will need a client for sending HTTP requests such as RESTed.

Before you get down to writing some code, you’ll start by getting some theoretical understanding of one of the most important concepts in iOS development, and asynchronous development in general, the Event Loop.

The Event Loop

Efficiency is an important goal when writing server-side code. The more efficient your code, the more users it can help concurrently. Where an unoptimized app can only help ten to a hundred users at a time, highly optimized apps are able to help a hundred-thousand concurrent users on the same hardware.

The main reason for this difference is architectural. A poorly optimized app will be idle most of the time, whereas a highly optimized app will try to spend its time cleverly and actively. The brains behind this architecture in server-side Swift is provided by SwiftNIO, Apple’s cross-platform event-driven networking framework, by a type called EventLoop.

EventLoop is, in its essence, a while loop that keeps looking to do work when an external factor, such as a user uploading a file, has made progress.

If one thread writes to a variable, and another thread tries to read it or write to it at the same time, a race condition occurs, which will crash your app. One method you can use to prevent race conditions from occurring is using locks for each of these variables. You’ll lock this lock before accessing a variable and unlock is after accessing it is completed, so any future accesses to the resource are prevented until you’ll unlock the lock. The main downside of this approach is its complexity and impact on performance.

Using a single thread to access this information is another way to tackle the issue of race conditions. Doing this means all read and write operations are done by this thread. One example of this is the database you’ll find in the sample project. Using this method requires you to give the read/write thread a way to return the result to the event loop that requested it.

If you respond to a request from a different EventLoop, SwiftNIO will crash the app to prevent undefined behaviour from causing trouble.

Futures and Promises

Future is a type used to describe information that does not exist yet, but will exist in the future. Writing asynchronous code with futures means directing the futures to respond to the successful results or to the failures. You can create a future with a predefined result: either success or failure. However, if the result is not known yet, you need to create a Promise.

These promises and futures need to be created from the EventLoop since the future type will always return to the event loop it originated from. A future can only hold one result at a time, at which point it is considered completed. Completion can be either successful or unsuccessful, thus failed futures are also considered completed. Finally, if a promise is creates, it must be completed.

With this short theoretical introduction, time to get your hands dirty with some code!

Getting Started

To start, click the Download Materials button at the top or bottom of this tutorial to download the project files. The starter project is set up with a web server and mocked database.

Open Terminal and navigate into the Starter project folder.

Open folder in Terminal

Take note of the Package.swift file in the Starter folder. This file is your app’s manifest file, describing its sources and dependencies. It is Swift Package Manager’s (SPM) equivalent of CocoaPods’ Podspec.

Run this command in Terminal:

swift package generate-xcodeproj

This downloads the dependencies of this project and generates an Xcode project file.

When SPM has finished, an Xcode project file appears in the project folder. Open it!

Select the BasicAsync target and make sure Xcode’s target device is set to My Mac.

Set the project target.

With that, you’re set up! Click the Run button in Xcode to start the web server. You should see the following output in the Xcode console:

Server started and listening

This means that you’re all set! Open your new website at http://localhost:8080 and welcome the future!

Server is running

The Quotes Repository

Now that you have a web server up and running, it is time to add some more interesting functionality that will allow you to see futures and promises in action. By the end of this tutorial, you will be able to create new quotes, list all or selected quotes and delete quotes.

The Repository Pattern is used to work with the Quote model. You will call QuoteRepository to operate on the quotes in the database. The repository needs the EventLoop so that it can create promises. The database is static so that it is shared between all repository instances.

You are going to edit two files: QuoteResponder.swift and QuoteRepository.swift.

QuoteResponder will receive the API requests from a browser, or from a different tool of your choice, such as cURL or RESTed, and work out what route is being called and whether it should create, list or delete quotes. The methods in this class will call the methods in QuoteRepository, which will send instructions to the database and then return promises to QuoteResponder. When the promises have completed asynchronously, QuoteResponder will wrap the successful or erroneous result into a HTTPResponse and send it back.

Setting up the Quotes Repository

First, in QuoteRepository.swift, add the following to the QuoteRepository class to handle the insertion of a new quote:

func insert(_ quote: Quote) -> EventLoopFuture<Void> {
  // 1
  let promise = eventLoop.newPromise(of: Void.self)
  // 2
  QuoteRepository.database.addEntity(quote, completing: promise)
  // 3
  return promise.futureResult
}

In the following code you:

  1. Create a promise with a Void return type that will indicate success or failure. The promise is created on the current EventLoop to ensure that the correct thread will receive the future’s result.
  2. Request the QuoteRepository to store the quote in the database, passing it the promise to use to indicate failure or success when finished.
  3. Finally, return the promise’s future to the route so that the route will receive the result of the database operation once it has completed asynchronously.

Add the following method to QuoteRepository as well:

func fetchAllQuotes() -> EventLoopFuture<[Quote]> {
  let promise = eventLoop.newPromise(of: [Quote].self)
  QuoteRepository.database.getAllEntities(completing: promise)
  return promise.futureResult
}

This method is very similar to the previous one, but it creates a promise wherein the return type is an array of Quotes. Then, it prompts the database to gather all the stored quotes, after which the method returns the promise’s future.