async/await in Swi!
Photo by Stephen H on Unsplash
Peter Friese | Firebase Developer Advocate | @pete!riese
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
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
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…
!
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
This is just the toolchain, really
Select toolchain here
Toolchain is active
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)
}
}
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
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
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)")
}
}
}
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) { !!% }
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
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
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
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
await: only from within async context
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
Run with confidence Engage users
Develop apps faster
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
auth!$signInAnonymously()
let user = auth!$currentUser
print("User signed in with user ID: (user!$uid)")
This might be nil
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
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
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
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
Here is how much work it was
to implement for Firebase:
.
(yep - it came almost for free)
Learn more
h!ps://pete"riese.dev/async-await-in-swi#
h!ps://www.youtube.com/watch?v=sEKw2BMcQtQ
Thanks!
Peter Friese
h!p://pete"riese.dev
@pete"riese
youtube.com/c/PeterFriese/
Follow me

async/await in Swift

  • 1.
    async/await in Swi! Photoby 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 { funcprocess(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 { funcprocess(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.
    This is justthe toolchain, really
  • 7.
  • 8.
    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) } }
  • 9.
    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
  • 10.
    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
  • 11.
    Sometimes, it’s morecomplicated, 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)") } } }
  • 12.
    Sometimes, it’s morecomplicated, 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) { !!% }
  • 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) } } } Introduce this wrapper
  • 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) } } } Call the original here
  • 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() } 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
  • 16.
    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
  • 18.
    await: only fromwithin async context
  • 19.
    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
  • 21.
    Run with confidenceEngage users Develop apps faster
  • 22.
    Run with confidence Crashlytics Performance Monitoring TestLab 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
  • 23.
    auth!$signInAnonymously() let user =auth!$currentUser print("User signed in with user ID: (user!$uid)") This might be nil
  • 24.
    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
  • 25.
    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
  • 26.
    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
  • 27.
    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
  • 28.
    Here is howmuch work it was to implement for Firebase: . (yep - it came almost for free)
  • 31.
  • 32.