Upgrade to Pro — share decks privately, control downloads, hide ads and more …

🍏 + 🔥 = ❤️ (Firebase for Apple Developers)

🍏 + 🔥 = ❤️ (Firebase for Apple Developers)

In this talk, I am going to walk you through the building blocks for creating modern applications on Apple’s platforms using SwiftUI and Firebase.

We will cover the following topics:

- Setting up an architecture for data-driven applications
- What’s new in SwiftUI’s application life cycle
- Syncing data in realtime across multiple devices with Cloud Firestore
- Mapping Firestore documents in a type-safe way using the Codable protocol
- Using Combine to fetch data from remote APIs and Firebase
- How async/await (new in Swift 5.5) will affect the way we write asynchronous code (plus some surprises in Firebase's code base)

Firebase is always evolving, so I will also talk about how we’re making use of Apple’s latest technologies in our own code base, for example

- Combine
- async/await
- SwiftUI view modifiers

No matter if you’re a seasoned Firebase user or just wondering what it is all about, you should leave this session with a deeper understanding of what Firebase it and how you can use it your apps on Apple’s platforms.

Peter Friese

May 20, 2021
Tweet

More Decks by Peter Friese

Other Decks in Technology

Transcript

  1. Peter Friese | Firebase Developer Advocate | @pete rf riese

     + Firebase for Apple Developers
  2. Challenge #1 This needs to be a binding But this

    isn’t a list of bindings
  3. struct BookShelfView: View { @Binding var bookShelf: BookShelf var body:

    some View { List { ForEach(Array(bookShelf.books.enumerated()), id: \.element.id) { index, item in BookRowView(book: $bookShelf.books[index]) } .onDelete { indexSet in bookShelf.books.remove(atOffsets: indexSet) } } .navigationTitle(bookShelf.title) } } Solution #1: iterate over enumerated items 🤔
  4. struct BookShelfView: View { @Binding var bookShelf: BookShelf var body:

    some View { List { ForEach($bookShelf.books) { $book in BookRowView(book: $book) } .onDelete { indexSet in bookShelf.books.remove(atOffsets: indexSet) } } .navigationTitle(bookShelf.title) } } Solution #1: use list bindings Everything is now bindable
  5. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  6. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  7. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  8. struct BookEditView: View { @Binding var book: Book @ObservedObject var

    bookEditViewModel: BookEditViewModel init(book: Binding<Book>) { self._book = book self.bookEditViewModel = BookEditViewModel(book: book.wrappedValue) } var body: some View { TextField("Book title", text: $bookEditViewModel.book.title) } func save() { self.book = bookEditViewModel.book dismiss() } } Solution 2: use inner @ObservableObject
  9. class BookEditViewModel: ObservableObject { @Published var book: Book @Published var

    isISBNValid: Bool = true init(book: Book) { self.book = book self.$book .map { checkISBN(isbn: $0.isbn) } .assign(to: &$isISBNValid) } } Solution 2: use inner @ObservableObject Bonus: use Combine to perform validation
  10. 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
  11. SwiftUI 2: No more AppDelegate! import SwiftUI @main struct BookShelfApp:

    App { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } } } }
  12. SwiftUI 2: No more AppDelegate! import SwiftUI @main struct BookShelfApp:

    App { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } } } }
  13. Solution 1: use initialiser import SwiftUI @main struct BookShelfApp: App

    { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") }
  14. Solution 1: use initialiser import SwiftUI @main struct BookShelfApp: App

    { @StateObject var store = BookShelfStore(shelves: BookShelf.samples) var body: some Scene { WindowGroup { NavigationView { BookShelvesView(store: store) Text("Select a shelf to see its books") Text("Select a book to see its details") } init() { FirebaseApp.configure() }
  15. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  16. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  17. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  18. Solution 2: use UIApplicationDelegateAdaptor import SwiftUI import Firebase class AppDelegate:

    NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) - > Bool { FirebaseApp.configure() return true } } @main struct BookShelfApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  19. Watch the scene phase Handle deep links Continue user activities

    Learn more pete rf riese.dev/ultimate-guide-to-swi ft ui2-application-lifecycle/
  20. bird_type: airspeed: coconut_capacity: isNative: icon: vector: distances_traveled: "swallow" 42.733 0.62

    false <binary data> { x: 36.4255, y: 25.1442, z: 18.8816 } [42, 39, 12, 42] Document
  21. struct Book: Identifiable { var id = UUID().uuidString var shelfId:

    String? var userId: String? var title: String var author: String var isbn: String var pages: Int var isRead: Bool = false var coverEditionKey: String? } Data Model
  22. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  23. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  24. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  25. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  26. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  27. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  28. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } Can we do better?
  29. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { if let document = document { let id = document.documentID let data = document.data() let title = data?["title"] as? String ?? "" let numberOfPages = data?["numberOfPages"] as? Int ?? 0 let author = data?["author"] as? String ? ? "" self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author) } } } } Fetching a document from Firestore
  30. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { } } } Mapping data using Codable if let document = document { do { self.book = try document.data(as: Book.self) } catch { print(error) } }
  31. func fetchBook(documentId: String) { let docRef = Firestore.firestore().collection("books").document(documentId) docRef.getDocument {

    document, error in if let error = error as NSError? { print("Error getting document: \(error.localizedDescription)") } else { } } } Mapping data using Codable if let document = document { do { self.book = try document.data(as: Book.self) } catch { print(error) } }
  32. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  33. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  34. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  35. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  36. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  37. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  38. class BookStore: ObservableObject { var db = Firestore.firestore() private var

    listenerRegistration: ListenerRegistration? @Published var books = [Book]() func subscribe() { listenerRegistration = db.collection("books") .addSnapshotListener { [weak self] (querySnapshot, error) in guard let documents = querySnapshot ?. documents else { return } self ?. books = documents.compactMap { queryDocumentSnapshot in let result = Result { try queryDocumentSnapshot.data(as: Book.self) } switch result { case .success(let book): if let book = book { return book Fetching a collection of documents
  39. import Foundation import Combine import Firebase import FirebaseFirestoreSwift import os

    class BookStore: ObservableObject { // MARK: - Dependencies var db = Firestore.firestore() // MARK: - Publishers @Published var user: User? @Published var books = [Book]() // MARK: - Private attributes @Published private var userId: String = "unknown" private var listenerRegistration: ListenerRegistration? private var cancellables = Set<AnyCancellable>() Fetching a collection of documents
  40. // A Book value could not be initialized from the

    DocumentSnapshot. switch error { case DecodingError.typeMismatch(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.valueNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.keyNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.dataCorrupted(let key): self ? . logger.debug("\(error.localizedDescription): \ (key.debugDescription)") default: self ? . logger.debug("Error decoding document: \ (error.localizedDescription)") } return nil } } } } Fetching a collection of documents
  41. // A Book value could not be initialized from the

    DocumentSnapshot. switch error { case DecodingError.typeMismatch(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.valueNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.keyNotFound(_, let context): self ? . logger.debug("\(error.localizedDescription): \ (context.debugDescription)") case DecodingError.dataCorrupted(let key): self ? . logger.debug("\(error.localizedDescription): \ (key.debugDescription)") default: self ? . logger.debug("Error decoding document: \ (error.localizedDescription)") } return nil } } } } Fetching a collection of documents about 100 lines of code
  42. struct BookShelfView: View { @FirestoreQuery( collectionPath: "books", predicates: [ .where("userId",

    isEqualTo: userId), ] ) var books: Result<[Book], Error> @State var userId = "F18EBA5E" var body: some View { List(books) { book in Text(book.title) } } } Firestore Property Wrapper Firebase 8.9.0 @FloWritesCode @mo rt enditlevsen Thanks to
  43. Sign in the user Update the data model Secure users’

    data How to implement Firebase Authentication?
  44. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  45. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  46. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  47. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple
  48. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple Firebase SDK
  49. SignInWithAppleButton( onRequest: { .. . }, onCompletion: { result in

    ... let appleIDToken = appleIDCredential.identityToken let idTokenString = String(data: appleIDToken, encoding: .utf8) let credential = OAuthProvider.credential(withProviderID: “apple.com", idToken: idTokenString, rawNonce: nonce) Auth.auth().signIn(with: credential) { (authResult, error) in if (error ! = nil) { .. . } print(“User signed in") dismiss() } } ).frame(width: 280, height: 45, alignment: .center) Sign in with Apple Firebase SDK
  50. let query = db.collection("books") .whereField("userId", isEqualTo: self.userId) query .addSnapshotListener {

    [weak self] (querySnapsho guard let documents = querySnapshot ?. documents els Signed in user
  51. rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match

    /{document=**} { allow create: if request.auth != null; allow read, update, delete: if request.auth != null && resource.data.userId = = request.auth.uid; } } } Security Rules Only signed-in users can create new documents Only owners may read and modify a document
  52. auth ?. signInAnonymously() let user = auth ?. currentUser print("User

    signed in with user ID: \(user ? . uid)") This might be nil
  53. 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)") } Do this instead
  54. 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): self.extractImage(from: url) { result in Problem: Callback Pyramid of Doom
  55. 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) } } Solution: Use async/await Swift 5.5
  56. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  57. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  58. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  59. func fetchArticle(from url: String) async throws -> String { guard

    let url = URL(string: url) else { throw AnalyserError.badURL } return try await withUnsafeThrowingContinuation { continuation in URLSession.shared.downloadTask(with: url) { (localUrl, urlResponse, error) in guard let localUrl = localUrl else { continuation.resume(throwing: AnalyserError.badURL) return } if let htmlText = try? String(contentsOf: localUrl) { continuation.resume(returning: htmlText) } } .resume() } } Solution: Use async/await Swift 5.5
  60. auth ?. signInAnonymously { result, error in guard let result

    = result else { return } print("User signed in with user ID: \(result.user.uid)") } do { let result = try await Auth.auth().signIn(withEmail: email, password: password) print("User signed in with user ID: \(result.user.uid)") } catch { print(error) } Works with Firebase, too! Callback-style
  61. Learn more h tt ps://pete rf riese.dev/async-await-in-swi f h tt

    ps://www.youtube.com/watch?v=sEKw2BMcQtQ
  62. Thanks! Peter Friese h tt p://pete rf riese.dev @pete rf

    riese 
 youtube.com/c/PeterFriese/ Follow me h tt ps://github.com/pete rf riese/BookShelf Code
  63. Credits fi nish by Megan Chown from the Noun Project

    Time by Nikita Kozin from the Noun Project pipe by Komkrit Noenpoempisut from the Noun Project Passpo rt by ProSymbols from the Noun Project spiral by Alexander Skowalsky from the Noun Project Architecture by Ervin Bolat from the Noun Project Firebase logos cou rt esy h tt ps:// fi rebase.google.com/brand-guidelines Firebase logos cou rt esy h tt ps:// fi rebase.google.com/brand-guidelines Thanks! Hea rt by Roman from the Noun Project