Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

async/await in Swift

async/await in Swift

Async/await is a new language feature that will ship with Swift 5.5 this year. There’s no doubt it will have a significant impact on how we write asynchronous code.

In this talk, we’re going to look at some use cases for async/await, how we can call existing Swift APIs using this new feature, and why your decision to write your SDK in Objective-C might turn out to have been a very clever move.

We’ll also have a look at the refactoring support Apple is adding to Xcode and how it will help you migrate your existing code base.

Peter Friese

June 04, 2021
Tweet

More Decks by Peter Friese

Other Decks in Technology

Transcript

  1. async/await in Swi! Photo by Stephen H on Unsplash Peter

    Friese | Firebase Developer Advocate | @pete!riese
  2. let req = URLRequest(url: URL(string: “https:!"yourapi/path“)!) let task = URLSession.shared.dataTask(with:

    req) { data, response, error in !" do stuff once data arrives } task.resume() Asynchronous code is everywhere completion handler
  3. func process(url: String, completion: @escaping (Article) !# Void) { self.fetchArticle(from:

    url) { html in self.extractTitle(from: html) { title in self.extractText(from: html) { text in self.extractImage(from: url) { imageUrl in self.inferTags(from: text) { tags in let article = Article(url: url, title: title, tags: tags, imageUrlString: imageUrl) completion(article) } } } } } } A more complex example
  4. extension ArticleAnalyser { func process(url: String, completion: @escaping (Article) !#

    Void) { self.fetchArticle(from: url) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let html): self.extractTitle(from: html) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let title): self.extractText(from: html) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let text): Let’s add some error handling… !
  5. extension AsyncArticleAnalyser { func process(url: String) async throws !# Article

    { let htmlText = try await fetchArticle(from: url) let text = try await extractText(from: htmlText) let title = try await extractTitle(from: htmlText) let imageUrl = try await extractImage(from: url) let tags = await inferTags(from: text) return Article(url: url, title: title, tags: tags, imageUrlString: imageUrl) } } How about this? Swift 5.5
  6. func helloWorld(name: String, completion: (String) !# Void) { completion("Hello, \(name)")

    } Let’s convert a callback-based function! func greeting() { helloWorld(name: "Peter") { result in print(result) } }
  7. func helloWorld(name: String, completion: (String) !# Void) { completion("Hello, \(name)")

    } Let’s convert a callback-based function! func helloWorld(name: String) async !# String { return "Hello, \(name)" } Before After
  8. func helloWorld(name: String, completion: (String) !# Void) { completion("Hello, \(name)")

    } Let’s convert a callback-based function! let result = await helloWorld(name: "Peter") func helloWorld(name: String) async !# String { return "Hello, \(name)" } Before After Call site
  9. Sometimes, it’s more complicated, though func getUser(id: Int, _ completion:

    @escaping (User?) !# Void) { let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } func greeting() { getUser(id: 1) { result in if let userName = result!$name { print("Hello, \(userName)") } } }
  10. Sometimes, it’s more complicated, though func getUser(id: Int, _ completion:

    @escaping (User?) !# Void) { let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } Let’s keep this entire function func getUser(id: Int, _ completion: @escaping (User?) !# Void) { !!% }
  11. func getUser(id: Int, _ completion: @escaping (User?) !# Void) {

    let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } Sometimes, it’s more complicated, though func getUser(id: Int, _ completion: @escaping (User?) !# Void) { !!% } func getUser(id: Int) async !# User? { return await withCheckedContinuation { cont in getUser(id: id) { result in cont.resume(returning: result) } } } Introduce this wrapper
  12. func getUser(id: Int, _ completion: @escaping (User?) !# Void) {

    let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } Sometimes, it’s more complicated, though func getUser(id: Int, _ completion: @escaping (User?) !# Void) { !!% } func getUser(id: Int) async !# User? { return await withCheckedContinuation { cont in getUser(id: id) { result in cont.resume(returning: result) } } } Call the original here
  13. func getUser(id: Int, _ completion: @escaping (User?) !# Void) {

    let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } Sometimes, it’s more complicated, though func getUser(id: Int, _ completion: @escaping (User?) !# Void) { !!% } func getUser(id: Int) async !# User? { return await withCheckedContinuation { cont in getUser(id: id) { result in cont.resume(returning: result) } } } Create a continuation Resume once we receive a result
  14. func getUser(id: Int, _ completion: @escaping (User?) !# Void) {

    let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } Sometimes, it’s more complicated, though func getUser(id: Int, _ completion: @escaping (User?) !# Void) { !!% } func getUser(id: Int) async !# User? { return await withCheckedContinuation { cont in getUser(id: id) { result in cont.resume(returning: result) } } } let result = await getUser(id: 1) Call site
  15. func getUser(id: Int, _ completion: @escaping (User?) !# Void) {

    let req = URLRequest(url: URL(string: "https:!"jsonplaceholder./users/\(id)")!) URLSession.shared.dataTask(with: req) { data, response, error in guard let data = data else { return } do { let user = try JSONDecoder().decode(User.self, from: data) completion(user) } catch { completion(nil) } }.resume() } await: only from within async context struct AddArticleView: View { @ObservedObject var viewModel: ArticlesViewModel @State var newUrl: String = "" func addUrl(url: String) { Task.detached { await viewModel.addNewArticle(from: url) } presentationMode.wrappedValue.dismiss() } var body: some View { } } Previously: @asyncHandler
  16. Run with confidence Crashlytics Performance Monitoring Test Lab App Distribution

    Engage users Analytics Predictions Cloud Messaging Remote Config A/B Testing Dynamic Links In-app Messaging Develop apps faster Auth Cloud Functions Cloud Firestore Hosting ML Kit Realtime Database Cloud Storage bit.ly/what-is-firebase Extensions Machine Learning
  17. auth!$signInAnonymously() let user = auth!$currentUser print("User signed in with user

    ID: \(user!$uid)") auth!$signInAnonymously { result, error in guard let result = result else { return } print("User signed in with user ID: \(result.user.uid)") } Use callbacks instead
  18. auth!$signInAnonymously() let user = auth!$currentUser print("User signed in with user

    ID: \(user!$uid)") auth!$signInAnonymously { result, error in guard let result = result else { return } print("User signed in with user ID: \(result.user.uid)") } @Published var user: User? !!% auth!$signInAnonymously() .map { $0.user } .replaceError(with: nil) .assign(to: &$user) Combine
  19. auth!$signInAnonymously { result, error in guard let result = result

    else { return } print("User signed in with user ID: \(result.user.uid)") } Firebase and async/await
  20. auth!$signInAnonymously { result, error in guard let result = result

    else { return } print("User signed in with user ID: \(result.user.uid)") } Firebase and async/await do { let result = try await auth!$signInAnonymously() print("User signed in with user ID: \(result.user.uid)") } catch { print(error) } Callback-style Async/await
  21. Here is how much work it was to implement for

    Firebase: . (yep - it came almost for free)