SwiftUI框架詳細解析 (十四) —— 基于Firebase Cloud Firestore的SwiftUI iOS程序的持久性添加(一)

版本記錄

版本號 時間
V1.0 2020.12.17 星期四

前言

今天翻閱蘋果的API文檔,發現多了一個框架SwiftUI,這里我們就一起來看一下這個框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細解析 (二) —— 基于SwiftUI的閃屏頁的創建(一)
3. SwiftUI框架詳細解析 (三) —— 基于SwiftUI的閃屏頁的創建(二)
4. SwiftUI框架詳細解析 (四) —— 使用SwiftUI進行蘋果登錄(一)
5. SwiftUI框架詳細解析 (五) —— 使用SwiftUI進行蘋果登錄(二)
6. SwiftUI框架詳細解析 (六) —— 基于SwiftUI的導航的實現(一)
7. SwiftUI框架詳細解析 (七) —— 基于SwiftUI的導航的實現(二)
8. SwiftUI框架詳細解析 (八) —— 基于SwiftUI的動畫的實現(一)
9. SwiftUI框架詳細解析 (九) —— 基于SwiftUI的動畫的實現(二)
10. SwiftUI框架詳細解析 (十) —— 基于SwiftUI構建各種自定義圖表(一)
11. SwiftUI框架詳細解析 (十一) —— 基于SwiftUI構建各種自定義圖表(二)
12. SwiftUI框架詳細解析 (十二) —— 基于SwiftUI創建Mind-Map UI(一)
13. SwiftUI框架詳細解析 (十三) —— 基于SwiftUI創建Mind-Map UI(二)

開始

首先看下主要內容:

在本教程中,您將學習如何使用Firebase Cloud Firestore將持久性添加到SwiftUI iOS應用程序。內容來自翻譯

接著看下主要內容:

Swift 5, iOS 14, Xcode 12

接著就是主要內容:

Google的移動后端服務,即Firebase,為應用程序開發人員提供了從分析到分發(Analytics to Distribution),再到數據庫和身份驗證(Databases and Authentication)的所有功能。在本教程中,您將了解Cloud Firestore(該服務套件的一部分)以及如何將其與SwiftUI結合使用。

Cloud Firestore是一個靈活的NoSQL云數據庫,開發人員可以使用它實時存儲和同步應用程序數據。您將使用它為FireCard提供數據和服務,該應用程序可通過創建卡片來幫助用戶記住概念。

在此過程中,您將學習如何:

  • Set up Firestore.
  • Use MVVM to structure an scalable code base.
  • Manipulate data using Combine and Firestore.
  • Use anonymous authentication.

打開入門項目。

Firecards是一個簡單的工具,可讓用戶通過提供問題和答案來創建卡片。稍后,他們可以通過閱讀問題并輕按卡片上的內容來測試自己的記憶力,以查看其答案是否正確。

目前,該應用沒有持久性存儲(persistence),因此用戶無法對其做太多事情。但是您可以通過將Firestore添加到此SwiftUI應用中來解決此問題。

您將不會使用它幾個步驟,而是在Xcode中打開FireCards.xcodeproj

注意:該項目使用Swift Package Manager來管理依賴項。由于Firebase SDK很大,因此建議您在閱讀本教程的同時在后臺打開項目,并讓其獲取并解決所有依賴項。

1. Setting Up Firebase

您必須先創建一個Firebase帳戶,然后才能使用Cloud Firestore。轉到Firebase網站。在右上角,單擊Go to console。然后提供您的Google帳戶憑據(Google account credentials);如果您還沒有憑據,請創建一個。

接下來,單擊+ Add project。將會出現一個modal,詢問您的項目名稱。類型FireCard

下一步要求您為項目啟用Google Analytics(分析)。 由于本教程沒有介紹Analytics(分析),因此請單擊底部的切換按鈕將其禁用。 然后單擊Create project

幾秒鐘后,您會看到一條消息,上面寫著Your new project is ready。 點擊Continue,您將看到項目的儀表板:

您可以在此處訪問所有Firebase服務。 選擇Add an app to get started的圓圈iOS按鈕開始使用。 在iOS Bundle ID字段中輸入com.raywenderlich.firecards,然后單擊Register app

按照概述的說明下載GoogleService-Info.plist并將其拖到FireCards Xcode項目中:

Xcode提示時,請確保選中Copy Items if needed

下一步要求您將Firebase SDK添加到您的iOS應用中,這已經為您完成。 單擊Next轉到Add initialization code步驟。

打開AppDelegate.swift。 通過添加以下導入語句,確保包括Firebase

import Firebase

接下來,將此代碼添加到application(_:didFinishLaunchingWithOptions :)中的return語句之前:

FirebaseApp.configure()

Firebase項目的網頁上,單擊Next,然后單擊Continue to console

這會將您帶回到項目的概述頁面:

您完成了Firebase的設置,并為您的應用授予了訪問所有Firebase服務的權限。 接下來,您將配置Cloud Firestore

2. Setting Up Cloud Firestore

在左側菜單的Develop下,單擊Cloud Firestore。 然后,單擊Create database

將出現一個modal,向您顯示下一步:

  • Cloud Firestore設置安全規則。
  • 設置Cloud Firestore位置。

Firebase使用安全規則來處理數據訪問授權。 選擇Start in test mode,這將使您的數據在未經授權的情況下可訪問30天。

盡管這對于測試項目是可以的,但是您應該始終設置適當的Security Rules。 幸運的是,您稍后將在Adding Authorization Using Security Rules中進行介紹。

點擊Next。 現在,助手將詢問您要將數據存儲在何處。

請注意,該位置可能會影響您的帳單,以后將無法更改。 現在,選擇nam5(us-central),然后單擊Enable

現在,Firebase將為您配置所有內容。 然后,它將把您轉到Firebase項目的Cloud Firestore部分:

在這里,您將了解如何實時插入,刪除或更新數據。 您也可以根據需要手動操作它。


Architecting the App Using MVVM

對于此項目,您將使用Model-View-View ModelMVVM來構建應用程序的組件。

MVVM是一種結構設計模式,它將構成應用程序的元素分為Views, View Models and Models。 這種設計模式有助于開發人員從視圖中分離業務邏輯,并保持必要的關注點分離,以使視圖和模型與數據源和業務邏輯不可知。

由于您將Cloud Firestore用于數據持久性,因此將添加一個層來處理與數據源進行交互所需的邏輯。 對于此項目,您將使用存儲庫模式。 下圖顯示了應用程序體系結構的最終表示形式:

  • Models保存應用程序數據。它們代表了您的應用需要管理的實體。
  • Views構成構成應用程序的視覺元素,并負責顯示模型中數據。
  • View Model通過轉換模型中的數據使其可以顯示在視圖中,從而使模型與視圖之間的關系成為可能。
  • Repository表示處理數據源data source通信的抽象。在這種情況下,數據源是Cloud Firestore。當View Model需要對數據進行任何操作時,它會與Repository進行通信,并通知有關數據更改的視圖。

1. Thinking in Collections and Documents

Cloud FirestoreNoSQL數據庫。它使用集合和文檔(collections and document)來構造數據。

集合保存文檔。這些文檔documents的字段構成了您應用程序的實體,在本例中為卡片。因此,卡片是文檔,卡片組是集合。

這是應用程序數據結構的直觀表示:

您可以編寫查詢(queries)以從集合中獲取數據,或插入,更新或刪除文檔。 為此,您需要使用唯一標識符創建對集合或特定文檔的引用。 創建新文檔時,您可以手動傳遞此標識符,否則Cloud Firestore會為您創建一個。

聊夠了,該寫代碼了!

2. Adding New Cards

首先創建Repository以訪問數據。

在項目導航器中,右鍵單擊Repositories,然后單擊New file…。 創建一個名為CardRepository.swift的新Swift文件,并向其中添加以下代碼:

// 1
import FirebaseFirestore
import FirebaseFirestoreSwift
import Combine 

// 2
class CardRepository: ObservableObject {
  // 3
  private let path: String = "cards"
  // 4
  private let store = Firestore.firestore()

  // 5
  func add(_ card: Card) {
    do {
      // 6
      _ = try store.collection(path).addDocument(from: card)
    } catch {
      fatalError("Unable to add card: \(error.localizedDescription).")
    }
  }
}

在這里:

  • 1) 導入FirebaseFirestore,FirebaseFirestoreSwiftCombineFirebaseFirestore使您可以訪問Firestore API,而Combine為Swift提供了一組聲明性API。

FirebaseFirestoreSwift添加了一些很酷的功能來幫助您將Firestore與模型集成。它使您可以將Cards轉換為文檔,并將文檔轉換為Cards

  • 2) 定義CardRepository并使遵循ObservableObjectObservableObject使用發布者幫助此類發出更改,因此其他對象可以偵聽并做出相應的反應。
  • 3) 然后,聲明path并分配cards的值。這是Firestore中的集合名稱。
  • 4) 聲明store并分配對Firestore實例的引用。
  • 5) 接下來,定義add(_ :)并使用do-catch塊捕獲由代碼引發的任何錯誤。如果在更新文檔時出了點問題,則會因致命錯誤終止應用的執行。
  • 6) 使用path創建對cards集合的引用,然后將card傳遞到addDocument(from:encoder:completion :)。這會將新卡添加到集合中。

使用上面的代碼,編譯器將報addDocument(from:encoder:completion :)要求Card符合Encodable。要解決此問題,請打開Card.swift并將類定義更改為此:

struct Card: Identifiable, Codable {

通過添加CodableSwift可以無縫地對Cards進行序列化和反序列化。 其中包括導致錯誤的Encodable和將Firestore文檔轉換為Swift對象時將使用的Decodable

3. Adding the View Model

您需要一個視圖模型才能將模型與視圖連接。 在Project導航器的ViewModels組下,創建一個名為CardListViewModel.swift的新Swift文件。

將此添加到新文件:

// 1
import Combine

// 2
class CardListViewModel: ObservableObject {
  // 3
  @Published var cardRepository = CardRepository()

  // 4
  func add(_ card: Card) {
    cardRepository.add(card)
  }
}

下面進行細分:

  • 1) Combine為您提供了處理異步代碼的API。
  • 2) 您聲明CardListViewModel并使它符合ObservableObject。 這使您可以偵聽此類型對象發出的更改。
  • 3) @Published為此屬性創建一個發布者,以便您可以訂閱它。
  • 4) 您將card傳遞到存儲庫,以便可以將其添加到集合中。

打開NewCardForm.swift并為您創建的view model添加一個屬性,緊隨NewCardForm中的其他屬性之后:

@ObservedObject var cardListViewModel: CardListViewModel

之前的更改將使Xcode Preview停止工作,因為現在NewCardForm需要一個CardListViewModel。 要解決此問題,請更新NewCardForm_Previews

static var previews: some View {
  NewCardForm(cardListViewModel: CardListViewModel())
}

NewCardForm的底部添加以下addCard()方法:

private func addCard() {
  // 1
  let card = Card(question: question, answer: answer)
  // 2  
  cardListViewModel.add(card)
  // 3
  presentationMode.wrappedValue.dismiss()
}

這段代碼:

  • 1) 使用已經在頂部聲明的questionanswer屬性創建Card
  • 2) 使用view model添加新card
  • 3) 關閉當前視圖。

然后,通過將Button(action:{}){替換為下面代碼,將此新方法稱為Add New Card的操作。

Button(action: addCard) {

最后,打開CardListView.swift,找到.sheet修飾符,并通過現在傳遞一個新的視圖模型實例來修復編譯器錯誤。 您稍后將使用共享實例。

.sheet(isPresented: $showForm) {
  NewCardForm(cardListViewModel: CardListViewModel())
}

構建并運行。

點擊右上角的+。 填寫question and answer字段,然后點擊Add New Card

嗯,什么都沒發生。卡片未顯示在主屏幕中:

在網絡瀏覽器中打開Firebase Console,然后轉到Cloud Firestore部分。 Firestore自動創建了cards收藏。 單擊標識符以導航到新文檔:

您的數據存儲在Firebase中,但您仍未實現檢索和顯示卡片的邏輯。


Retrieving and Displaying Cards

現在該展示您的卡片了! 首先,您需要創建一個view model來代表一張Card

在項目導航器的ViewModels下,創建一個名為CardViewModel.swift的新Swift文件。

將此添加到新文件:

import Combine

// 1
class CardViewModel: ObservableObject, Identifiable {
  // 2
  private let cardRepository = CardRepository()
  @Published var card: Card
  // 3
  private var cancellables: Set<AnyCancellable> = []
  // 4
  var id = ""

  init(card: Card) {
    self.card = card
    // 5
    $card
      .compactMap { $0.id }
      .assign(to: \.id, on: self)
      .store(in: &cancellables)
  }
}

在這里:

  • 1) 聲明CardViewModel并使它與ObservableObject兼容,以便它可以發出更改和Identifiable,從而保證您可以迭代CardViewModels的數組。
  • 2) 這為實際的card模型提供了引用。 @Published為此屬性創建一個發布者,以便您可以訂閱它。
  • 3) cancellables用于存儲您的訂閱,因此您以后可以取消訂閱。
  • 4) id是要求遵循Identifiable的屬性。 它應該是唯一的標識符。
  • 5) 在卡的ID和視圖模型的ID之間為card設置綁定。 然后將對象存儲在cancellables對象中,以便以后可以取消。

1. Setting Up the Repository

您的存儲庫需要處理獲取卡的邏輯。 打開CardRepository.swift并在屬性定義下方的頂部添加以下代碼:

// 1
@Published var cards: [Card] = []

// 2
init() {
  get()
}

func get() {
  // 3
  store.collection(path)
    .addSnapshotListener { querySnapshot, error in
      // 4
      if let error = error {
        print("Error getting cards: \(error.localizedDescription)")
        return
      }

      // 5
      self.cards = querySnapshot?.documents.compactMap { document in
        // 6
        try? document.data(as: Card.self)
      } ?? []
    }
}

在上面的代碼中,您:

  • 1) 定義cards@Published為此屬性創建一個發布者,以便您可以訂閱它。 每次修改此數組時,所有偵聽者都會做出相應的反應。
  • 2) 創建初始化方法并調用get()
  • 3) 使用path獲取對集合根目錄的引用,并添加一個偵聽器以接收集合中的更改。
  • 4) 檢查是否發生錯誤,打印錯誤消息并返回。
  • 5) 在querySnapshot.documents上使用compactMap(_ :)遍歷所有元素。 如果querySnapshotnil,則改為設置一個空數組。
  • 6) 使用data(as:decoder :)將每個文檔映射為Card。 您可以這樣做,這要歸功于您在頂部導入的FirebaseFirestoreSwift,并且Card符合Codable

2. Setting Up CardListViewModel

接下來,打開CardListViewModel.swift并將這兩個屬性添加到CardListViewModel

// 1
@Published var cardViewModels: [CardViewModel] = []
// 2
private var cancellables: Set<AnyCancellable> = []

在此代碼中,您:

  • 1) 使用@Published屬性包裝器定義cardViewModels,以便您可以訂閱它。 它將包含CardViewModels數組。
  • 2) 創建一組AnyCancellables。 它將用于存儲您的訂閱,以便您以后可以取消訂閱。

仍在視圖模型中時,添加以下初始化程序:

init() {
  // 1
  cardRepository.$cards.map { cards in
    cards.map(CardViewModel.init)
  }
  // 2
  .assign(to: \.cardViewModels, on: self)
  // 3
  .store(in: &cancellables)
}

您添加的代碼:

  • 1) 監聽cards,并將數組的每個Card元素映射到CardViewModel中。 這將創建一個CardViewModels數組。
  • 2) 將前一個映射操作的結果分配給cardViewModels
  • 3) 將此預訂的實例存儲在cancellables的對象中,以便在取消初始化CardListViewModel時自動將其取消。

3. Setting Up CardView

打開CardView.swift并進行以下更改。

var card: Card替換為:

var cardViewModel: CardViewModel

這使視圖可以直接使用視圖模型而不是Card模型。

然后,在frontView中,將card.question替換為:

cardViewModel.card.question

接下來,在backView中,將card.answer替換為:

cardViewModel.card.answer

最后,將CardView_Previews更改為此:

struct CardView_Previews: PreviewProvider {
  static var previews: some View {
    let card = testData[0]
    return CardView(cardViewModel: CardViewModel(card: card))
  }
}

進行了這些更改后,您現在可以直接傳遞預期的CardViewModel而不是Card模型。 但是,您需要再進行一次更新才能再次使用預覽。

4. Setting Up CardListView

您還需要更改包裝清單視圖,以便它與card view model一起使用。

打開CardListView.swift并將cards array屬性替換為:

@ObservedObject var cardListViewModel = CardListViewModel()

有了這一更改,CardListView現在期望使用CardListViewModel而不是Cards數組。 @ObservedObject將訂閱該屬性,以便它可以偵聽視圖模型中的更改。

在主體中查找ForEach語句,并將其更改為如下所示:

ForEach(cardListViewModel.cardViewModels) { cardViewModel in
  CardView(cardViewModel: cardViewModel)
    .padding([.leading, .trailing])
}

現在,您將遍歷cardListViewModel的各個卡片視圖模型,并為每個模型創建一個CardView

由于CardListView現在需要CardListViewModel而不是Cards數組,因此將CardListView_Previews更改為:

CardListView(cardListViewModel: CardListViewModel())

構建并運行

根據需要添加任意數量的卡片,并查看它們如何立即顯示在主屏幕上。


Updating Cards

該應用程序可讓用戶在獲得正確答案時進行標記。 如果不是這種情況,則會彈出一條消息,告訴他們上次嘗試失敗。

打開Card.swift并修改id,如下所示:

@DocumentID var id: String?

注意:這樣做會更改您的數據模型,因為id不會包含在其中。 下次您運行該應用程序時,以前的模型將不會顯示。

在頂部添加此導入語句:

import FirebaseFirestoreSwift

使用此代碼,您可以確保當FirebaseSDK將文檔轉換為Card時,Cloud Firestore中使用的Document Id會映射到id。 要對單個文檔執行操作,您需要使用其document id對其進行引用。

打開CardRepository.swift并將下一個方法添加到CardRepository

func update(_ card: Card) {
  // 1
  guard let cardId = card.id else { return }

  // 2
  do {
    // 3
    try store.collection(path).document(cardId).setData(from: card)
  } catch {
    fatalError("Unable to update card: \(error.localizedDescription).")
  }
}

這段代碼:

  • 1) 檢查card.id是否具有值。
  • 2) 捕獲代碼生成的任何異常。 如果在更新文檔時出現問題,則該應用程序將終止并顯示致命錯誤。
  • 3) 使用pathcardId,它獲取對cards集合中文檔的引用,然后通過將card傳遞給setData(from:encoder:completion :)來更新字段。

現在,您需要更新視圖模型。 打開CardViewModel.swift并將以下方法添加到CardViewModel

func update(card: Card) {
  cardRepository.update(card)
}

打開CardView.swift。 在frontView中的第二個Spacer()之后添加以下代碼:

if !cardViewModel.card.successful {
  Text("You answered this one incorrectly before")
    .foregroundColor(.white)
    .font(.system(size: 11.0))
    .fontWeight(.bold)
    .padding()
}    

如果card的屬性successful等于false,則此代碼顯示一條消息。

在繼續之前,將以下三種方法添加到CardView

// 1
private func markCardAsUnsuccesful() {
  var updatedCard = cardViewModel.card
  updatedCard.successful = false
  update(card: updatedCard)
}

// 2
private func markCardAsSuccesful() {
  var updatedCard = cardViewModel.card
  updatedCard.successful = true
  update(card: updatedCard)
}

// 3
func update(card: Card) {
  cardViewModel.update(card: card)
  showContent.toggle()
}

該代碼提供了兩種方法來處理成功和失敗的case,以及一種用于更新card的方法。

每種方法的作用如下:

  • 1) 將cardViewModel.card復制到updatedCard并將successful設置為false。 然后調用update(card :)
  • 2) 將cardViewModel.card復制到UpdatedCard并將successful設置為true。 然后調用update(card :)
  • 3) 將更新的卡片傳遞給update(card :),以便視圖模型可以更新模型。 然后在showContent上調用toggle()來觸發翻轉動畫。

接下來,將backView替換為以下內容:

var backView: some View {
  VStack {
    // 1
    Spacer()
    Text(cardViewModel.card.answer)
      .foregroundColor(.white)
      .font(.body)
      .padding(20.0)
      .multilineTextAlignment(.center)
      .animation(.easeInOut)
    Spacer()
    // 2
    HStack(spacing: 40) {
      Button(action: markCardAsSuccesful) {
        Image(systemName: "hand.thumbsup.fill")
          .padding()
          .background(Color.green)
          .font(.title)
          .foregroundColor(.white)
          .clipShape(Circle())
      }
      Button(action: markCardAsUnsuccesful) {
        Image(systemName: "hand.thumbsdown.fill")
          .padding()
          .background(Color.blue)
          .font(.title)
          .foregroundColor(.white)
          .clipShape(Circle())
      }
    }
    .padding()
  }
  .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
}

在這里,您添加了兩個新按鈕,以便用戶可以指示他們是否正確回答了問題。

構建并運行。

點擊任意卡,然后點擊拇指向下thumb-down圖標。 在底部,前視圖顯示一條消息,提示You answered this one incorrectly before


Removing Cards

用戶應能夠在需要時取出卡。

打開CardRepository.swift并在CardRepository的底部定義remove(_ :),如下所示:

func remove(_ card: Card) {
  // 1
  guard let cardId = card.id else { return }

  // 2
  store.collection(path).document(cardId).delete { error in
    if let error = error {
      print("Unable to remove card: \(error.localizedDescription)")
    }
  }
}

這段代碼:

  • 1) 檢查card.id是否具有值并將其存儲在cardId中。
  • 2) 使用pathcardId獲取對Cards集合中文檔的引用,然后調用delete。 這將從Cloud Firestore中的集合中刪除文檔。

delete(completion :)還提供了一個閉包,您可以在其中處理任何錯誤。 閉包中的代碼檢查是否有錯誤,并將其打印到控制臺。

打開CardViewModel.swift并向其中添加此方法,以便您的視圖模型可以調用CardRepository上的remove(_ :),并傳遞實際的Card

func remove() {
  cardRepository.remove(card)
}

最后,打開CardView.swift并在Alert內部的primaryButton的尾隨閉包中添加cardViewModel.remove(),因此如下所示:

Alert(
  title: Text("Remove Card"),
  message: Text("Are you sure you want to remove this card?"),
  primaryButton: .destructive(Text("Remove")) {
    cardViewModel.remove()
  },
  secondaryButton: .cancel())

這將在cardViewModel上調用remove()。 然后,視圖模型執行邏輯以從數據庫中刪除卡。

構建并運行。

將您的任何卡片拖到頂部。 出現alert,要求您確認操作。 點擊Remove,您的卡將消失。


Securing the Data

安全性對于任何應用程序都是必不可少的。 Firebase提供了一組身份驗證方法,可用于讓用戶對您的應用程序進行身份驗證。 對于本項目,您將實現匿名身份驗證Anonymous Authentication

匿名身份驗證(Anonymous Authentication)是一種身份驗證類型,可讓您為尚未注冊應用的用戶創建臨時帳戶,從而為他們提供了一層安全保護。 與安全規則(Security Rules)結合使用,匿名身份驗證為此應用程序提供了足夠的安全性。

要激活此身份驗證模式,請轉到Firebase控制臺,在左側邊欄中選擇Authentication,然后在頂部導航欄上選擇Sign-in method。 轉到Providers List的底部,選擇Anonymous,然后單擊右側的開關將其啟用。 最后,單擊Save

注意:如果您沒有看到頂部的導航欄,請單擊Get Started以跳過介紹性屏幕。

現在,您需要創建一個身份驗證服務。

1. Creating an authentication service

在項目導航器中,在Services下創建一個新的Swift文件,并將其命名為AuthenticationService.swift

將以下代碼添加到新文件中:

import Firebase

// 1
class AuthenticationService: ObservableObject {
  // 2
  @Published var user: User?
  private var authenticationStateHandler: AuthStateDidChangeListenerHandle?

  // 3
  init() {
    addListeners()
  }

  // 4
  static func signIn() {
    if Auth.auth().currentUser == nil {
      Auth.auth().signInAnonymously()
    }
  }

  private func addListeners() {
    // 5
    if let handle = authenticationStateHandler {
      Auth.auth().removeStateDidChangeListener(handle)
    }

    // 6
    authenticationStateHandler = Auth.auth()
      .addStateDidChangeListener { _, user in
        self.user = user
      }
  }
}

這段代碼:

  • 1) 聲明AuthenticationService并將其符合ObservableObject
  • 2) 定義在身份驗證過程發生時將包含User對象的用戶。它還定義了一個authenticationStateHandler屬性,以捕獲用戶對象中的更改,例如,當用戶登錄或注銷時。
  • 3) 實現init()并調用addListeners(),以便在實例化該類時調用它。
  • 4) 添加signIn(),用于登錄FirebaseAuthFirebase用戶對象存儲在currentUser中。

通過檢查它是否為nil,可以避免不必要的調用。此值存儲在本地,因此在第一次使用后,該應用使用同一用戶。

  • 5) 檢查是否已實例化處理程序,如果已實例化,則將其刪除。
  • 6) 將addStateDidChangeListener(_ :)監聽器分配給authenticationStateHandler

好的,您已經設置了身份驗證服務(Authentication Service)

2. Using the authentication service

打開AppDelegate.swift并在application(_:didFinishLaunchingWithOptions:)FirebaseApp.configure()之后添加以下行:

AuthenticationService.signIn()

此代碼可確保用戶在應用啟動時登錄。

接下來,打開CardRepository.swift并將這些屬性添加到類的頂部:

// 1
var userId = ""
// 2
private let authenticationService = AuthenticationService()
// 3
private var cancellables: Set<AnyCancellable> = []

這段代碼:

  • 1) 聲明userId,您將使用該ID存儲Firebase生成的當前用戶ID。
  • 2) 創建AuthenticationService的實例。
  • 3) 創建一組AnyCancellables。 此屬性存儲您的訂閱,因此您以后可以取消訂閱。

接下來,將init()更改為此:

init() {
  // 1
  authenticationService.$user
    .compactMap { user in
      user?.uid
    }
    .assign(to: \.userId, on: self)
    .store(in: &cancellables)

  // 2
  authenticationService.$user
    .receive(on: DispatchQueue.main)
    .sink { [weak self] _ in
      // 3
      self?.get()
    }
    .store(in: &cancellables)
}

在這里:

  • 1) 將用戶ID從AuthenticationService綁定到存儲庫的userId。 它還將對象存儲在cancellables中,以便以后可以取消。
  • 2) 該代碼觀察用戶user的變化,使用receive(on:options :)設置代碼執行的線程,然后使用sink(receiveValue:)附加訂閱者。 這樣可以保證,當您從AuthenticationService獲取用戶user時,閉包中的代碼將在主線程中執行。
  • 3) 像在原始的初始化程序中一樣,調用get()

add(_ :)更改為此:

func add(_ card: Card) {
  do {
    var newCard = card
    newCard.userId = userId
    _ = try store.collection(path).addDocument(from: newCard)
  } catch {
    fatalError("Unable to add card: \(error.localizedDescription).")
  }
}

在這里,您制作了card的副本,并將其userId更改為存儲庫的userId的值。 現在,每次創建新卡時,它都會包含Firebase生成的實際用戶id

最后,在get().addSnapshotListener(_ :)之前添加以下行:

.whereField("userId", isEqualTo: userId)

此代碼使您可以按userId過濾卡片。

3. Adding Authorization Using Security Rules

在網絡瀏覽器中打開Firebase項目。 然后轉到Cloud Firestore,然后單擊頂部水平導航欄上的Rules。 您會看到類似以下內容:

此類似于JavaScript的代碼是Firestore Security Rules。 這些規則定義用戶是否有權訪問或修改文檔。 用以下代碼替換現有代碼:

// 1
rules_version = '2';
// 2
service cloud.firestore {
  // 3 
  match /databases/{database}/documents {
    // 4
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

這是做什么的:

  • 1) 將rules_version設置為“ 2”。 目前,這是最新版本,并確定如何解釋以下代碼。
  • 2) 指示這些規則適用于哪些服務。 在這種情況下,請使用Cloud Firestore
  • 3) 指定規則應匹配項目中的任何Cloud Firestore數據庫。
  • 4) 指定僅經過身份驗證的用戶可以讀取或寫入文檔。

這些規則確定了應用程序的授權部分。 通過身份驗證,您可以了解用戶是誰。 通過授權,您可以確定該用戶可以做什么。

單擊Publish以保存更改。 然后回到項目并構建并運行。

該應用目前已按用戶userId進行過濾,因此不會顯示任何卡片。如果您添加一個新的,它將出現。您甚至可以關閉并重新打開該應用程序,僅顯示從現在開始創建的卡片。


Understanding Firestore Pricing

了解Cloud Firestore的定價可以節省您花費過多的金錢。請記住以下幾點:

  • Cloud Firestore向您收取您執行的操作數:讀取,寫入和刪除。
  • 價格從一個地點到另一個地點有所不同。
  • 您還必須支付數據庫使用的存儲空間和網絡帶寬。
  • 如果更改單個字段或完整的文檔,則視為一次操作。
  • 讀取次數是返回的記錄數。因此,如果您的查詢返回了十個文檔,那么您將有十次讀取。如果可能,請使用limit限制查詢可以返回的文檔數。
  • 您可以使用Alerts監控當前預算。您可以使用Google Cloud Console對其進行配置,這也可以讓您檢查以前的發票并設置所需的每日支出。
  • Google Cloud Operation可讓您監控效果并獲取指標,這些指標也可以幫助您制定預算。

如果可能,您還應該在本地緩存數據,以避免從Firestore請求數據。

您可以在the Firestore documentation文檔中找到更多信息。

在本教程中,您學習了如何使用Cloud Firestore持久存儲數據以及如何使用MVVM將其與SwiftUI視圖集成。 您還從頭開始學習了如何使用Firebase實施Anonymous Authentication

FirebaseCloud Firestore提供了更多功能。 如果您想更深入地了解Cloud Firestore,請查看官方的official Cloud Firestore documentation。 或查看Firebase Tutorial: Getting StartedFirebase Tutorial: Real-time ChatVideo Tutorial: Beginning Firebase

后記

本篇主要講述了基于Firebase Cloud FirestoreSwiftUI iOS程序的持久性添加,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容