Modern Concurrency: Getting Started

Oct 18 2022 Swift 5.5, iOS 15, Xcode 13.4

Part 1: Asynchronous Code

3. Your First Asynchronous App

Episode complete

Play next episode

Next
Save for later
About this episode
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 2. Getting Started Next episode: 4. Asynchronous Sequences

This video Your First Asynchronous App was last updated on Oct 18 2022

Run the course server

Most of the projects in this course interact with a server. It’s included in the course materials.

Open the course materials folder in Finder and find the CourseServer folder. CourseServer is a Vapor server that you run in Terminal, so open Terminal next to your Finder window.

First, you need to change directory to CourseServer: In Terminal, type cd then drag the CourseServer folder from Finder into Terminal and press Return.

Type ls to list the files in CourseServer. This Package.swift file is what you’ll run to start the server.

Type swift run to run the Swift package. This takes a few minutes to fetch all the dependencies and build the server.

When you see this Server starting... message, check the connection by opening a browser window and loading localhost:8080/hello.

Stop the server by pressing Control-C. And restart it with swift run: Press the up-arrow to get swift run back then press Return. Restarting is quick because you already downloaded everything.

If you leave this server running for a while, it might stop serving data correctly. If an app doesn’t work as you expect, stop and restart the course server.

LittleJohn app

For the rest of this part of the course, you’ll work on this simple ticker app. In this episode, you’ll fetch a list of stock symbols from the course server.

The Live ticker button is disabled until you select a list item.

In episode 5, you’ll make this Live ticker screen show live stock prices. Well, apparently live prices. These are actually random numbers you’ll download from the course server.

Fetch stock symbols

In your browser, reload the server page to check it’s still running.

In the course materials, locate the LittleJohn starter project and open it.

In the project navigator, open the Views and Model groups.

Starter projects in this course have all the views, navigation and data model already set up, so you can concentrate on writing the concurrency code to make the app work.

The views you’ll be working on are outside the Views group, for quick access.

Your work on this first app will just give you a feel for what asynchronous code looks like. Don’t worry about understanding how it works. You’ll learn all that in future episodes.

In this episode, you’ll work on the code that fetches a list of stock symbols from the server.

SymbolListView will display this list by calling a method in Model/LittleJohnModel.

Non-async code in starter

In LittleJohnModel, locate availableSymbols():

func availableSymbols() async throws -> [String] {
  guard let url = URL(string: "http://localhost:8080/littlejohn/symbols")
  else {
    throw "The URL could not be created."
  }
  return []
}

When this method completes, it returns an array of strings. For now, it returns an empty array, so the compiler doesn’t flag an error.

The throws keyword is the usual warning that this method can throw errors, which it does in the guard statement.

The method tries to create a URL from the server’s littlejohn/symbols endpoint. This fails if the server isn’t connected, so it throws an error message.

At the top of this file is a String extension that lets you throw strings instead of creating custom error types. This simple error-handling lets you focus on the concurrency code.

Add async call: async throws / try await

The async keyword indicates this function contains a suspension point, which you’ll add now… just before the return statement:

let (data, response) = try await URLSession.shared.data(from: url)
🟥return []

You saw this at the end of the previous episode.

But this time, you want to check the response, too.

Unlike the old dataTask(with:) method, data(from:) can throw errors.

First you await the completion of the async method. This could throw errors.

So then you check for thrown errors. This is a big advantage of using data(from:) instead of dataTask(with:completionHandler:): You can’t forget to handle errors.

Handle data, response

Next, you need to verify the server response and return the fetched data. Replace the dummy return line with this code:

let (data, response) = try await URLSession.shared.data(from: url)
🟩
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
  throw "The server responded with an error."
}
return try JSONDecoder().decode([String].self, from: data)

This is pretty much what you would write in a dataTask(with:) completion handler, except you wouldn’t be able to throw any errors. Also, you’re not writing a completion handler! You’re effectively writing synchronous code that will execute when your asynchronous task returns.

To check your new method compiles, press Command-B not Command-R.

Call async method from SwiftUI view

Before you run the app, you need to update the SwiftUI view to use your new async method.

In SymbolListView, scroll down and start adding this code where the TODO is, just below .padding(.horizontal):

.padding(.horizontal)
// TODO: Call model.availableSymbols()
🟩
.onAppear {
  symbols = try await model.availableSymbols() 
}

If Xcode doesn’t immediately complain, press Command-B to get this error message:

Invalid conversion from 'async' function of type '() async throws -> ()' to synchronous function type '() -> Void'

SwiftUI view modifiers like onAppear(...) run code synchronously, and you’re trying to call your new asynchronous method in a non-concurrent context. Fortunately, Swift concurrency has a solution for this scenario.

Replace onAppear with task:

.🟩task🟥 {
  symbols = try await model.availableSymbols()
}

Remember the capital-T Task you used in the playground? It let you run asynchronous code in the playground’s synchronous context.

This task view modifier lets you call asynchronous functions in the SwiftUI view’s synchronous context.

But now there’s a new problem: It doesn’t throw, so you must handle any errors here:

Wrap it in a do-catch:

.task {
  do {
    symbols = try await model.availableSymbols()
  } catch  {

  }
}

If the warnings don’t go away, press Command-B to get rid of them. task-do-catch is a standard code block for calling an async throwing function from a SwiftUI view. You’ll use it in episode 5, too.

Like onAppear, task runs when the view appears on screen. But, if you’ve already fetched your symbols, you don’t want it to run again.

So add this line before the do-catch block:

.task {
  🟩guard symbols.isEmpty else { return }🟥
  do {

And tie up the catch loose end with this line:

} catch  {
  🟩lastErrorMessage = error.localizedDescription🟥
}

Scroll up to see what this lastErrorMessage is.

The views in this app are already set up to display an alert whenever lastErrorMessage changes.

OK, now build and run in an iPhone simulator. And there’s your list of stock symbols. Well done!

Test error handling

How about testing whether SymbolListView catches an error thrown by availableSymbols()?

Stop the app in Xcode. Use the app switcher to show Terminal, then press Control-C to stop the server. And build and run again.

There’s the alert with the error message Could not connect to the server. This isn’t even one of the strings you wrote into availableSymbols(). data(from:) throws it when it fails to connect to the server.

In Terminal, restart the server: Press up-arrow to get swift run back then press return. Wait for the Server starting message.

Build and run again, and your list is back. Select a stock symbol, then tap Live ticker.

Not much to see here yet. You’ll implement this view after the next episode.