Slide 1

Slide 1 text

async/await in Swi! Photo by Stephen H on Unsplash Peter Friese | Firebase Developer Advocate | @pete!riese

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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… !

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

This is just the toolchain, really

Slide 7

Slide 7 text

Select toolchain here Toolchain is active

Slide 8

Slide 8 text

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) } }

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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)") } } }

Slide 12

Slide 12 text

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) { !!% }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

await: only from within async context

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Run with confidence Engage users Develop apps faster

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

auth!$signInAnonymously() let user = auth!$currentUser print("User signed in with user ID: \(user!$uid)") This might be nil

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Here is how much work it was to implement for Firebase: . (yep - it came almost for free)

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Learn more h!ps://pete"riese.dev/async-await-in-swi# h!ps://www.youtube.com/watch?v=sEKw2BMcQtQ

Slide 32

Slide 32 text

Thanks! Peter Friese h!p://pete"riese.dev @pete"riese youtube.com/c/PeterFriese/ Follow me