A Practical Introduction to Task and Await

This post introduces how to execute code asynchronously in Swift. First, it gives a general overview of the topic, then two concrete code examples demonstrate how to use asynchronous code in practical, real-world scenarios.

The code examples are written for Xcode Playgrounds and can be copied, pasted, and run directly.

Both requests target reqbin.com, an online REST and SOAP API testing tool.

The first example uses an HTTP GET request, which can also be tried out online on their site.

The second example uses an HTTP POST request with a JSON payload, shown here.

The post also covers basic JSON encoding and decoding.

Asynchronous Code Execution

Before we get into sending HTTP requests, let’s look at asynchronous code execution.

The Swift documentation describes “Asynchronous Code” as follows:

Asynchronous code can be suspended and resumed later, although only one piece of the program executes at a time. Suspending and resuming code in your program lets it continue to make progress on short-term operations like updating its UI while continuing to work on long-running operations like fetching data over the network or parsing files.

To make a long-running function asynchronous, mark it with the async keyword.

func myLongRunningFunction() async {
	...
 // do something that takes lots of time
...
}

To call this function, we need to use the await keyword.

await myLongRunningFunction()

Let’s take a specific example of Task.sleep(). In the code below, we want to wait 5 seconds.

print("Before")
try Task.sleep(nanoseconds: 5 * 1_000_000_000)
print("Done")
print("After")

This code cannot be compiled. We get the error message: “‘async’ call in a function that does not support concurrency”.

Because the main thread of our Xcode Playground is synchronous, we need to call async beforehand.

print("Before")
async {
	try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
	print("Done")
}
print("After")

While this code runs fine now, it is only a matter of time before it fails to compile.

The reason is that async is deprecated and will be replaced by Task.init.

Let’s quickly change that.

print("Before")
Task {
	try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
	print("Done")
}
print("After")

Running this will produce the following output.

Before
After
Done

Whereas the “Before” and “After” will be printed immediately, it takes 5 seconds for “Done” to be printed.

Now that we have the basic picture of asynchronous code execution, let us look at how to use that to send HTTP requests.

Sending HTTP GET Request

Here is example code for sending an HTTP GET request.

We need to do three steps:

  • Create the request using the URL
  • Send the request
  • Evaluate the response
import PlaygroundSupport
import UIKit
 
struct SuccessInfo: Decodable {
  let success: String
}
 
guard let url = URL(string: "https://reqbin.com/echo/get/json") else { fatalError("Missing URL") }
 
var urlRequest = URLRequest(url: url)
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
 
Task {
  let (data, response) = try await URLSession.shared.data(for: urlRequest)
  guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
 
  let successInfo = try JSONDecoder().decode(SuccessInfo.self, from: data)
 
  print(String(data: data, encoding:.utf8)?? "default value")
  print("Success: \(successInfo.success)")
}
 
print("I am here already!")
 
PlaygroundPage.current.needsIndefiniteExecution = true

The important classes are:

“A value that identifies the location of a resource, such as an item on a remote server or the path to a local file.”

“A URL load request that is independent of protocol or URL scheme.”

“An object that coordinates a group of related, network data transfer tasks.”

“The metadata associated with the response to an HTTP protocol URL load request.”

Sending an HTTP POST Request

This section shows an HTTP POST request with a JSON payload. We still perform the same three steps as in the first example; the main difference here is that we send data to the server.

Our data is encoded in MyJsonObject. You can use any payload you want—this backend ignores the content.

Most REST services respond with the same resource type, so we could have used MyJsonObject as the single type and made it conform to Codable.

import PlaygroundSupport
import UIKit
 
struct MyJsonObject: Encodable {
  let id: Int
  let foo: String
  let bar: String
}
 
struct SuccessInfo: Decodable {
  let success: String
}
 
let myJsonObject = MyJsonObject(id: 1, foo: "Hello", bar: "World")
let payload = try JSONEncoder().encode(myJsonObject)
 
guard let url = URL(string: "https://reqbin.com/echo/post/json") else { fatalError("Missing URL") }
 
var urlRequest = URLRequest(url: url)
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpMethod = "POST"
 
Task {
  let (data, response) = try await URLSession.shared.upload(for: urlRequest, from: payload)
   
  guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
   
  let successInfo = try JSONDecoder().decode(SuccessInfo.self, from: data)
   
  print(String(data: data, encoding:.utf8)?? "default value")
  print("Success: \(successInfo.success)")
}
 
print("I am here already!")
 
PlaygroundPage.current.needsIndefiniteExecution = true

Let’s look at the differences between Encodable, Decodable, and Codable.

“An object that decodes instances of a data type from JSON objects.”

“A type that can encode itself to an external representation.”

“A type that can decode itself from an external representation.”

“A type that can convert itself into and out of an external representation.”

Running the Code

When running the example code, you might see that the line “I am here already!” is printed before the HTTP response body.

Conclusion

Following a brief introduction to writing asynchronous Swift code with async and await, two practical examples demonstrate how to send and receive data asynchronously via HTTP GET and POST requests.

Feel free to copy and paste the code into an Xcode playground and experiment.

Thank you for reading!

Resources