2021 Swift
Meet async/await in Swift
Swift now supports asynchronous functions — a pattern commonly known as async/await. Discover how the new syntax can make your code easier to read and understand. Learn what happens when a function suspends, and find out how to adapt existing completion handlers to asynchronous functions.
Watch at developer.apple.com ↗Code shown on screen · 11 snippets
Writing a function using completion handlers
func fetchThumbnail(for id: String, completion: @escaping (UIImage?, Error?) -> Void) {
let request = thumbnailURLRequest(for: id)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(nil, error)
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(nil, FetchError.badID)
} else {
guard let image = UIImage(data: data!) else {
completion(nil, FetchError.badImage)
return
}
image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
guard let thumbnail = thumbnail else {
completion(nil, FetchError.badImage)
return
}
completion(thumbnail, nil)
}
}
}
task.resume()
} Using completion handlers with the Result type
func fetchThumbnail(for id: String, completion: @escaping (Result<UIImage, Error>) -> Void) {
let request = thumbnailURLRequest(for: id)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(.failure(FetchError.badID))
} else {
guard let image = UIImage(data: data!) else {
completion(.failure(FetchError.badImage))
return
}
image.prepareThumbnail(of: CGSize(width: 40, height: 40)) { thumbnail in
guard let thumbnail = thumbnail else {
completion(.failure(FetchError.badImage))
return
}
completion(.success(thumbnail))
}
}
}
task.resume()
} Using async/await
func fetchThumbnail(for id: String) async throws -> UIImage {
let request = thumbnailURLRequest(for: id)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
let maybeImage = UIImage(data: data)
guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
return thumbnail
} Async properties
extension UIImage {
var thumbnail: UIImage? {
get async {
let size = CGSize(width: 40, height: 40)
return await self.byPreparingThumbnail(ofSize: size)
}
}
} Async sequences
for await id in staticImageIDsURL.lines {
let thumbnail = await fetchThumbnail(for: id)
collage.add(thumbnail)
}
let result = await collage.draw() Testing using XCTestExpectation
class MockViewModelSpec: XCTestCase {
func testFetchThumbnails() throws {
let expectation = XCTestExpectation(description: "mock thumbnails completion")
self.mockViewModel.fetchThumbnail(for: mockID) { result, error in
XCTAssertNil(error)
expectation.fulfill()
}
wait(for: [expectation], timeout: 5.0)
}
} Testing using async/await
class MockViewModelSpec: XCTestCase {
func testFetchThumbnails() async throws {
XCTAssertNoThrow(try await self.mockViewModel.fetchThumbnail(for: mockID))
}
} Bridging from sync to async
struct ThumbnailView: View {
var viewModel: ViewModel
var post: Post
private var image: UIImage?
var body: some View {
Image(uiImage: self.image ?? placeholder)
.onAppear {
Task {
self.image = try? await self.viewModel.fetchThumbnail(for: post.id)
}
}
}
} Async APIs in the SDK
import ClockKit
extension ComplicationController: CLKComplicationDataSource {
func currentTimelineEntry(for complication: CLKComplication) async -> CLKComplicationTimelineEntry? {
let date = Date()
let thumbnail = try? await self.viewModel.fetchThumbnail(for: post.id)
guard let thumbnail = thumbnail else {
return nil
}
let entry = self.createTimelineEntry(for: thumbnail, date: date)
return entry
}
} Async alternatives and continuations
// Existing function
func getPersistentPosts(completion: @escaping ([Post], Error?) -> Void) {
do {
let req = Post.fetchRequest()
req.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
let asyncRequest = NSAsynchronousFetchRequest<Post>(fetchRequest: req) { result in
completion(result.finalResult ?? [], nil)
}
try self.managedObjectContext.execute(asyncRequest)
} catch {
completion([], error)
}
}
// Async alternative
func persistentPosts() async throws -> [Post] {
typealias PostContinuation = CheckedContinuation<[Post], Error>
return try await withCheckedThrowingContinuation { (continuation: PostContinuation) in
self.getPersistentPosts { posts, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: posts)
}
}
}
} Storing the continuation for delegate callbacks
class ViewController: UIViewController {
private var activeContinuation: CheckedContinuation<[Post], Error>?
func sharedPostsFromPeer() async throws -> [Post] {
try await withCheckedThrowingContinuation { continuation in
self.activeContinuation = continuation
self.peerManager.syncSharedPosts()
}
}
}
extension ViewController: PeerSyncDelegate {
func peerManager(_ manager: PeerManager, received posts: [Post]) {
self.activeContinuation?.resume(returning: posts)
self.activeContinuation = nil // guard against multiple calls to resume
}
func peerManager(_ manager: PeerManager, hadError error: Error) {
self.activeContinuation?.resume(throwing: error)
self.activeContinuation = nil // guard against multiple calls to resume
}
} Resources
Related sessions
-
13 min -
25 min -
29 min -
18 min -
27 min -
23 min -
22 min -
14 min -
27 min -
14 min -
29 min -
28 min -
17 min -
33 min -
1h 1m -
39 min -
9 min