02. The use of Continuation

It will take about 3 minutes to finish reading this article.

Continuation is a mechanism used in Swift to handle asynchronous programming that helps developers write and manage asynchronous code more easily. In asynchronous programming, developers usually need to handle the results of asynchronous operations through callbacks, delegates, closures and other mechanisms. This can lead to poor code readability, difficult to manage and maintain, and is prone to Callback Hell. Continuations aims to solve these problems and provide a cleaner way of asynchronous programming.

Continuation provides two functions, withUnsafeContinuation and withUnsafeThrowingContinuation, that allow callback-based APIs to be invoked from within the asynchronous code.Each of these functions receives an operation closure that will invoke the callback-based API.The closure receives a Continuation instance, which must be recovered by the callback to provide either the result value or (in the Throwing variant) the thrown error, which becomes the result of the withUnsafeContinuation call when the asynchronous task is resumed.

Sample code one:

1
2
3
4
5
6
7
8
9
class CheckedContinuationBootcampNetworkManager {

func getHeartImageFromDatabase(completionHandler: @escaping (_ image: UIImage) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
completionHandler(UIImage(systemName: "heart.fill")!)
}
}

}

The calling Code is as follows:

1
2
3
4
let networkManager = CheckedContinuationBootcampNetworkManager()
networkManager.getHeartImageFromDatabase(completionHandler: { image in
self.image = image
})

The Code that using the Continuation is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CheckedContinuationBootcampNetworkManager {

func getHeartImageFromDatabase(completionHandler: @escaping (_ image: UIImage) -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
completionHandler(UIImage(systemName: "heart.fill")!)
}
}

//withCheckedContinuation is added here
func getHeartImageFromDatabase() async -> UIImage {
await withCheckedContinuation { continuation in
getHeartImageFromDatabase { image in
continuation.resume(returning: image)
}
}
}

}

The calling code is as follows:

1
self.image = await networkManager.getHeartImageFromDatabase()

The Code below looks more cleaner.

Sample code two:

1
2
3
4
5
6
7
8
func getData(url: URL) async throws -> Data {
do {
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
return data
} catch {
throw error
}
}

After the optimization of the ‘withCheckedThrowingContinuation’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func getData2(url: URL) async throws -> Data {
return try await withCheckedThrowingContinuation { continuation in
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
continuation.resume(returning: data)
} else if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(throwing: URLError(.badURL))
}
}
.resume()
}
}

Note:
In the continuation callback, ‘resume’ must be guaranteed once.

Reference

[1] https://github.com/apple/swift-evolution/blob/main/proposals/0300-continuation.md
[2] https://www.youtube.com/watch?v=Tw_WLMIfEPQ&list=PLwvDm4Vfkdphr2Dl4sY4rS9PLzPdyi8PM&index=8