Mapowanie danych Cloud Firestore za pomocą Swift Codable

Opracowany przez firmę Swift interfejs Codable API umożliwia nam wykorzystanie potęgi kompilatora, który ułatwia mapowanie danych z formatów serializowanych na format Swift .

Być może używasz Codable do mapowania danych z internetowego interfejsu API na dane swojej aplikacji (i odwrotnie), ale jest on znacznie bardziej elastyczny.

W tym przewodniku przyjrzymy się, jak można wykorzystać kod Codable do mapowania danych z Cloud Firestore do Swift i odwrotnie.

Podczas pobierania dokumentu z Cloud Firestore aplikacja otrzyma komunikat słownika par klucz-wartość (lub tablicy słowników, jeśli używasz jednego z operacji zwracających wiele dokumentów).

Obecnie z pewnością nadal możesz bezpośrednio korzystać ze słowników w języku Swift, a oferują dużą elastyczność, która może dokładnie odpowiadać Twoim potrzebom. Takie podejście nie jest bezpieczne i łatwo je przedstawić, trudne do wyśledzenia błędów z powodu błędnej pisowni nazw atrybutów lub zapomnienia zmapowania nowy atrybut dodany przez Twój zespół przy wdrażaniu tej nowej, ekscytującej funkcji. w zeszłym tygodniu.

W przeszłości wielu deweloperów obchodziło się z tymi niedociągnięciami, i wdrożyli prostą warstwę mapowania, która umożliwiałaby mapowanie słowników na Typy Swift. Jak się okazuje, większość z tych implementacji jest również oparta na ręcznym określenie mapowania między dokumentami Cloud Firestore a odpowiednich typów modelu danych aplikacji.

Dzięki obsłudze interfejsu Codable API Swift w Cloud Firestore sprawia, że może to mieć prostsza:

  • Nie będzie już trzeba ręcznie implementować kodu mapowania.
  • Zmapowanie atrybutów o różnych nazwach jest proste.
  • Ma wbudowane wsparcie dla wielu typów plików Swift.
  • Możesz też łatwo dodać obsługę mapowania typów niestandardowych.
  • A co najlepsze: w przypadku prostych modeli danych nie trzeba pisać żadnych kodu mapowania.

Dane mapowania

Cloud Firestore przechowuje dane w dokumentach mapujących klucze na wartości. Do pobrania z pojedynczego dokumentu, możemy go nazwać DocumentSnapshot.data(), który zwraca słownik z mapowaniem nazw pól na klucz Any: func data() -> [String : Any]?

Oznacza to, że możemy użyć składni indeksu dolnego Swift, aby uzyskać dostęp do poszczególnych pól.

import FirebaseFirestore

#warning("DO NOT MAP YOUR DOCUMENTS MANUALLY. USE CODABLE INSTEAD.")
func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)

  docRef.getDocument { document, error in
    if let error = error as NSError? {
      self.errorMessage = "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)
      }
    }
  }
}

Choć może się wydawać, że to proste i łatwe, ten kod jest delikatny, są trudne w utrzymaniu i podatne na błędy.

Jak widać, przyjmujemy założenia dotyczące typów danych w dokumencie . Te dane mogą nie być prawidłowe.

Nie ma schematu, więc możesz łatwo dodać nowy dokument i wybrać inny typ pola. Możesz przypadkowo wybierzesz ciąg dla pola numberOfPages, co spowoduje jeśli chodzi o trudną do odnalezienia kwestię map. Trzeba też zaktualizować mapowanie podczas dodawania nowego pola, co jest dość uciążliwe.

Nie zapominajmy, że nie korzystamy z silnej technologii Swift , który zna dokładnie typ właściwy dla każdej z właściwości Book

Co to w ogóle jest Codable?

Według dokumentacji Apple Codable to „typ, który potrafi sam się konwertować do i z zewnętrznej reprezentacji”. Codable jest aliasem typu. dla protokołów Encodable i Dekodable. Dostosowując typ Swift do tego przez protokół, kompilator syntezuje kod niezbędny do zakodowania/dekodowania wystąpienie tego typu w formacie zserializowanym, takim jak JSON.

Prosty typ przechowywania danych o książce może wyglądać tak:

struct Book: Codable {
  var title: String
  var numberOfPages: Int
  var author: String
}

Jak widać, dostosowanie typu do Codable jest minimalnie inwazyjne. My tylko musi dodać zgodność do protokołu; nie były wymagane żadne inne zmiany.

Dzięki temu możemy łatwo zakodować książkę na obiekt JSON:

do {
  let book = Book(title: "The Hitchhiker's Guide to the Galaxy",
                  numberOfPages: 816,
                  author: "Douglas Adams")
  let encoder = JSONEncoder()
  let data = try encoder.encode(book)
} 
catch {
  print("Error when trying to encode book: \(error)")
}

Dekodowanie obiektu JSON na instancję Book działa w ten sposób:

let decoder = JSONDecoder()
let data = /* fetch data from the network */
let decodedBook = try decoder.decode(Book.self, from: data)

Mapowanie do prostych typów w dokumentach Cloud Firestore
za pomocą Codable

Cloud Firestore obsługuje szeroką gamę typów danych, od prostych do map zagnieżdżonych. Większość z nich bezpośrednio odpowiada wbudowanemu interfejsowi Swift . Zanim przejdziemy do zmapowania kilku prostych typów danych, do bardziej złożonych.

Aby zmapować dokumenty Cloud Firestore na typy Swift, wykonaj te czynności:

  1. Sprawdź, czy do projektu dodano platformę FirebaseFirestore. Za pomocą za pomocą menedżera pakietów Swift lub CocoaPods aby to zrobić.
  2. Zaimportuj FirebaseFirestore do pliku Swift.
  3. Upewnij się, że Twój typ to Codable.
  4. (Opcjonalnie, jeśli chcesz użyć tego typu w widoku List) Dodaj id do swojego typu i użyć @DocumentID, aby wskazać Cloud Firestore i zmapować go na identyfikator dokumentu. Omówimy to bardziej szczegółowo poniżej.
  5. Użyj documentReference.data(as: ), aby zmapować odwołanie do dokumentu na klucz Swift typu.
  6. Użyj funkcji documentReference.setData(from: ), aby zmapować dane z typów Swift na Dokument Cloud Firestore.
  7. (Opcjonalne, ale zdecydowanie zalecane) Zaimplementuj prawidłową obsługę błędów.

Zaktualizujmy odpowiednio typ Book:

struct Book: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
}

Ponieważ ten typ można było już kodować, musieliśmy tylko dodać właściwość id i dodać do niej adnotacje za pomocą otoki właściwości @DocumentID.

Wykorzystując poprzedni fragment kodu do pobierania i mapowania dokumentu, możemy zastąp cały ręczny kod mapowania jednym wierszem:

func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)

  docRef.getDocument { document, error in
    if let error = error as NSError? {
      self.errorMessage = "Error getting document: \(error.localizedDescription)"
    }
    else {
      if let document = document {
        do {
          self.book = try document.data(as: Book.self)
        }
        catch {
          print(error)
        }
      }
    }
  }
}

Możesz też napisać to bardziej zwięźle, określając typ dokumentu. podczas rozmowy z numerem getDocument(as:). Spowoduje to wykonanie mapowania za Ciebie, zwraca typ Result zawierający zmapowany dokument lub błąd w przypadku, gdy Błąd dekodowania:

private func fetchBook(documentId: String) {
  let docRef = db.collection("books").document(documentId)
  
  docRef.getDocument(as: Book.self) { result in
    switch result {
    case .success(let book):
      // A Book value was successfully initialized from the DocumentSnapshot.
      self.book = book
      self.errorMessage = nil
    case .failure(let error):
      // A Book value could not be initialized from the DocumentSnapshot.
      self.errorMessage = "Error decoding document: \(error.localizedDescription)"
    }
  }
}

Aby zaktualizować istniejący dokument, wystarczy zadzwonić documentReference.setData(from: ) Oto kilka podstawowych sposobów obsługi błędów to kod zapisu instancji Book:

func updateBook(book: Book) {
  if let id = book.id {
    let docRef = db.collection("books").document(id)
    do {
      try docRef.setData(from: book)
    }
    catch {
      print(error)
    }
  }
}

Gdy dodasz nowy dokument, Cloud Firestore automatycznie zajmie się przypisując mu nowy identyfikator dokumentu. Działa to nawet wtedy, gdy aplikacja jest Użytkownik obecnie offline.

func addBook(book: Book) {
  let collectionRef = db.collection("books")
  do {
    let newDocReference = try collectionRef.addDocument(from: self.book)
    print("Book stored with new document reference: \(newDocReference)")
  }
  catch {
    print(error)
  }
}

Oprócz mapowania prostych typów danych Cloud Firestore obsługuje także innych typów danych. Niektóre są uporządkowanymi typami danych, których możesz używać do tworzenie zagnieżdżonych obiektów w dokumencie.

Zagnieżdżone typy niestandardowe

Większość atrybutów, które chcemy mapować w naszych dokumentach, to proste wartości, takie jak tytuł książki lub nazwisko autora. A co z takimi przypadkami, gdy musimy przechowywać bardziej złożony obiekt? Na przykład możemy przechowywać adresy URL z okładką książki w różnych rozdzielczościach.

Najłatwiejszym sposobem wykonania tej czynności w Cloud Firestore jest użycie mapy:

Przechowywanie zagnieżdżonego typu niestandardowego w dokumencie Firestore

Podczas zapisywania odpowiedniej struktury w formacie Swift możemy skorzystać z tego, że Cloud Firestore obsługuje adresy URL – jeśli przechowujesz pole zawierające adres URL, zostaje ono zostanie skonwertowana na ciąg znaków i na odwrót:

struct CoverImages: Codable {
  var small: URL
  var medium: URL
  var large: URL
}

struct BookWithCoverImages: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var cover: CoverImages?
}

Zwróć uwagę na to, jak zdefiniowaliśmy strukturę (CoverImages) dla mapy okładki w tabeli Dokument Cloud Firestore. Oznaczając właściwość okładki na BookWithCoverImages jako opcjonalne, jesteśmy w stanie obsłużyć fakt, że niektóre dokumenty mogą nie zawierać atrybutu okładka.

Jeśli zastanawiasz się, dlaczego nie ma fragmentu kodu służącego do pobierania lub aktualizowania danych, ucieszy Cię fakt, że nie ma potrzeby dostosowywania kodu pod kątem czytania lub zapisywanie danych z/do Cloud Firestore. Wszystko to działa dzięki kodowi, który opisanej w sekcji początkowej.

Tablice

Czasami chcemy zapisać kolekcję wartości w dokumencie. Gatunki Dobrym przykładem jest książka, np. Autostopem przez galaktykę. można zaliczyć do kilku kategorii, np. „Sci-Fi”. i „Komedia”:

Przechowywanie tablicy w dokumencie Firestore

W Cloud Firestore można to modelować za pomocą tablicy wartości. To jest obsługiwane w przypadku dowolnego typu kodowania (np. String, Int itp.). Poniżej pokazuje, jak dodać tablicę gatunków do naszego modelu Book:

public struct BookWithGenre: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var genres: [String]
}

Ta opcja działa w przypadku każdego rodzaju kodowania, więc możemy też używać typów niestandardowych. Wyobraźnia chcemy zapisać listę tagów dla każdej książki. Oprócz nazwy chcemy również zapisać jego kolor, jak w przykładzie:

Przechowywanie tablicy typów niestandardowych w dokumencie Firestore

Aby przechowywać tagi w ten sposób, wystarczy zaimplementować strukturę Tag są reprezentowane przez tag i pozwalają na kodowanie:

struct Tag: Codable, Hashable {
  var title: String
  var color: String
}

W ten sposób możemy przechowywać tablicę Tags w naszych dokumentach Book.

struct BookWithTags: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var tags: [Tag]
}

Kilka słów o mapowaniu identyfikatorów dokumentów

Zanim przejdziemy do mapowania kolejnych typów, porozmawiamy o mapowaniu identyfikatorów dokumentów przez chwilę.

W niektórych poprzednich przykładach wykorzystaliśmy kod usługi @DocumentID. , aby zmapować identyfikator naszych dokumentów Cloud Firestore na właściwość id ze struktur Swift. Jest to ważne z kilku powodów:

  • Pomaga nam określić, który dokument należy zaktualizować, jeśli użytkownik ustawił lokalny algorytm zmian.
  • Funkcja List w SwiftUI wymaga, by elementy miały postać Identifiable, aby można było zapobiega przeskakiwaniu elementów po ich wstawieniu.

Warto zaznaczyć, że atrybut oznaczony jako @DocumentID nie zostanie zakodowane przez koder Cloud Firestore podczas zapisywania dokumentu. To jest bo identyfikator dokumentu nie jest atrybutem samego dokumentu. zapisywanie go w dokumencie byłoby błędem.

Podczas pracy z typami zagnieżdżonymi (np. tablica tagów w interfejsie Book w parametrze poprzedniego przykładu w tym przewodniku), nie trzeba dodawać @DocumentID właściwość: właściwości zagnieżdżone są częścią dokumentu Cloud Firestore, nie stanowią oddzielnego dokumentu. Z tego względu nie potrzebują identyfikatora dokumentu.

Daty i godziny

Cloud Firestore ma wbudowany typ danych do obsługi dat i godzin obsługi. dzięki obsłudze platformy Codable przez Cloud Firestore korzystanie z nich.

Spójrzmy na ten dokument, który przedstawia mamę wszystkich języków programowania, Ada, opracowanego w 1843 roku:

Przechowywanie dat w dokumencie Firestore

Typ Swift do mapowania tego dokumentu może wyglądać tak:

struct ProgrammingLanguage: Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
}

Nie możemy opuścić tej sekcji dotyczącej dat i godzin bez rozmowy o @ServerTimestamp. Ten kod obiektu jest niezwykle przydatny, jeśli chodzi o do obsługi sygnatur czasowych w aplikacji.

W każdym systemie rozproszonym zegary poszczególnych systemów mogą nie są cały czas w pełni zsynchronizowane. Może Ci się wydawać, że to niewiele ale wyobraźmy sobie, jak czas może nie w systemie obrotu akcjami: nawet odchylenie milisekundowe może spowodować różnicę miliona dolarów z transakcji handlu.

Cloud Firestore obsługuje atrybuty oznaczone etykietą @ServerTimestamp jako to: jeśli atrybut ma wartość nil w chwili jego przechowywania (za pomocą addDocument() dla atrybutu przykładowy), Cloud Firestore wypełni to pole bieżącym serwerem jej sygnatury czasowej w momencie zapisu w bazie danych. Jeśli pole jest inne niż nil gdy zadzwonisz pod numer addDocument() lub updateData(), usługa Cloud Firestore zostanie zamknięta wartość atrybutu pozostaje bez zmian. Dzięki temu można łatwo wdrożyć takie pola, createdAt i lastUpdatedAt.

Punkty geograficzne

Geolokalizacja jest powszechna w naszych aplikacjach. Powstało wiele ciekawych funkcji przechowując je. Przechowywanie lokalizacji zadania może na przykład być przydatne. dzięki czemu aplikacja będzie Ci przypominać o zadaniu, gdy dotrzesz do miejsca docelowego.

Cloud Firestore ma wbudowany typ danych GeoPoint, który może przechowywać długości i szerokości geograficznej dowolnej lokalizacji. Aby zmapować lokalizacje z/do dokumentu Cloud Firestore, możemy użyć typu GeoPoint:

struct Office: Codable {
  @DocumentID var id: String?
  var name: String
  var location: GeoPoint
}

Typ w formacie Swift to CLLocationCoordinate2D. Możemy zmapować używając następującej operacji:

CLLocationCoordinate2D(latitude: office.location.latitude,
                      longitude: office.location.longitude)

Aby dowiedzieć się więcej o wysyłaniu zapytań o dokumenty według lokalizacji fizycznej, zapoznaj się z tym przewodniku po rozwiązaniu.

Wartości w polu enum

Wyliczenia są chyba jedną z najrzadziej niedocenianych funkcji językowych w Swift. kryją się za nimi o wiele więcej, niż się wydaje. Typowym zastosowaniem wyliczeń jest które pozwalają modelować określone stany pewnego obiektu. Na przykład możemy napisać aplikację zarządzania artykułami. Do śledzenia stanu artykułu możemy użyć funkcji wyliczenie Status:

enum Status: String, Codable {
  case draft
  case inReview
  case approved
  case published
}

Cloud Firestore nie obsługuje natywnie wyliczenia (tzn. nie może wymusić zbiór wartości), ale nadal możemy korzystać z tego, że wyliczenia można wpisywać, i wybrać typ kodowania. W tym przykładzie wybraliśmy String, co oznacza, że wszystkie wartości wyliczeniowe zostaną zmapowane na ciąg znaków, jeśli są przechowywane w Dokument Cloud Firestore.

A ponieważ Swift obsługuje niestandardowe, nieprzetworzone wartości, możemy nawet określić, . Jeśli na przykład zdecydowaliśmy się przechowywać plik Status.inReview sprawa została oznaczona jako „w trakcie sprawdzania”, możemy po prostu zaktualizować powyższe wyliczenie jako następujące:

enum Status: String, Codable {
  case draft
  case inReview = "in review"
  case approved
  case published
}

Dostosowywanie mapowania

Czasami nazwy atrybutów dokumentów Cloud Firestore, które chcemy są niezgodne z nazwami właściwości w naszym modelu danych w Swift. Na przykład jeden z naszych współpracowników może programować w języku Python i postanowił wybierz snake_case dla wszystkich nazw atrybutów.

Bez obaw – Codable będzie w stanie pomóc.

W takich przypadkach możemy użyć atrybutu CodingKeys. To wyliczenie, dodaj do obiektu Codable struct, aby określić sposób mapowania określonych atrybutów.

Przeanalizuj ten dokument:

Dokument Firestore z nazwą atrybutu snake_cased

Aby zmapować ten dokument na strukturę z właściwością name typu String, dodaj wyliczenie CodingKeys do struktury ProgrammingLanguage i określ nazwa atrybutu w dokumencie:

struct ProgrammingLanguage: Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
  
  enum CodingKeys: String, CodingKey {
    case id
    case name = "language_name"
    case year
  }
}

Domyślnie interfejs Codable API będzie używać nazw właściwości naszych typów Swift do określać nazwy atrybutów w dokumentach Cloud Firestore, które próbujemy na mapie. Nie ma potrzeby dodawania atrybutów, o ile nazwy atrybutów są takie same CodingKeys na nasze typy doorna. Jeśli jednak użyjesz nazwy CodingKeys dla atrybutu konkretnego typu, musimy dodać wszystkie nazwy miejsc, które mają być mapowane.

We fragmencie kodu powyżej zdefiniowaliśmy właściwość id, której możemy użyć użyć jako identyfikatora w widoku List w SwiftUI. Jeśli nie określiliśmy go w CodingKeys, nie zostanie zmapowany podczas pobierania danych, więc stanie się nil. W ten sposób widok List zostanie wypełniony pierwszym dokumentem.

Wszystkie usługi, które nie są wymienione jako przypadki w odpowiednim wyliczeniu CodingKeys zostaną zignorowane podczas procesu mapowania. Jest to wygodne, jeśli chcemy wykluczyć niektóre usługi z mapowania.

Jeśli na przykład chcemy wykluczyć właściwość reasonWhyILoveThis z grupy nie są już mapowane, wystarczy usunąć je z wyliczenia CodingKeys:

struct ProgrammingLanguage: Identifiable, Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
  var reasonWhyILoveThis: String = ""
  
  enum CodingKeys: String, CodingKey {
    case id
    case name = "language_name"
    case year
  }
}

Czasem możemy chcieć zapisać pusty atrybut z powrotem w Dokument Cloud Firestore. W Swift zastosowano pojęcie opcjonalne, które oznacza brak wartości, a Cloud Firestore obsługuje również wartości null. Domyślne działanie w przypadku opcjonalnych kodowania, które mają wartość nil, to aby je po prostu pominąć. Dzięki @ExplicitNull możemy określić, jak firma Swift Opcjonalne są obsługiwane podczas kodowania: oznaczając właściwość opcjonalną jako @ExplicitNull, możemy poprosić Cloud Firestore o zapisanie tej właściwości w dokument z wartością null, jeśli zawiera on wartość nil.

za pomocą niestandardowego kodera i dekodera do mapowania kolorów.

Ostatnim tematem w naszym omówieniu danych do mapowania w Codable jest wprowadzenie. z użyciem niestandardowych koderów i dekoderów. Ta sekcja nie obejmuje reklam natywnych Typ danych Cloud Firestore, ale niestandardowe kodery i dekodery są powszechnie używane w aplikacjach Cloud Firestore.

„Jak mogę przyporządkować kolory?” to jedno z najczęstszych pytań deweloperów, nie tylko w Cloud Firestore, ale też na potrzeby mapowania Swift i JSON na cóż. Istnieje wiele rozwiązań, ale większość z nich koncentruje się na formacie JSON, a niemal wszystkie z nich mapują kolory jako zagnieżdżony słownik składający się z RGB

Wydaje się, że powinno być lepsze, prostsze rozwiązanie. Dlaczego nie używamy kolorów z internetu (lub, ściślej stosując szesnastkowy zapis kolorów CSS) – są łatwe w użyciu. (głównie ciąg znaków) i obsługują nawet przezroczystość.

Aby zmapować obiekt Swift Color na jego wartość szesnastkową, trzeba utworzyć rozszerzenie, które dodaje kod Codable do Color.

extension Color {

 init(hex: String) {
    let rgba = hex.toRGBA()

    self.init(.sRGB,
              red: Double(rgba.r),
              green: Double(rgba.g),
              blue: Double(rgba.b),
              opacity: Double(rgba.alpha))
    }

    //... (code for translating between hex and RGBA omitted for brevity)

}

extension Color: Codable {
  
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let hex = try container.decode(String.self)

    self.init(hex: hex)
  }
  
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(toHex)
  }

}

Za pomocą funkcji decoder.singleValueContainer() możemy zdekodować element String Color, bez konieczności zagnieżdżania komponentów RGBA. Dodatkowo: używać tych wartości w interfejsie internetowym aplikacji bez konieczności ich konwertowania. pierwszy!

Dzięki temu możemy zaktualizować kod tagów mapowania, by ułatwić obsługę oznaczanie kolorów bezpośrednio bez konieczności ręcznego mapowania w kodzie interfejsu aplikacji:

struct Tag: Codable, Hashable {
  var title: String
  var color: Color
}

struct BookWithTags: Codable {
  @DocumentID var id: String?
  var title: String
  var numberOfPages: Int
  var author: String
  var tags: [Tag]
}

Obsługa błędów

W powyższych fragmentach kodu celowo ograniczyliśmy obsługę błędów do minimum, ale w wersji produkcyjnej warto jednak upewnić się, że bezproblemowo .

Oto fragment kodu, który pokazuje, jak obsługiwać w przypadku wystąpienia błędów może pojawić się:

class MappingSimpleTypesViewModel: ObservableObject {
  @Published var book: Book = .empty
  @Published var errorMessage: String?
  
  private var db = Firestore.firestore()
  
  func fetchAndMap() {
    fetchBook(documentId: "hitchhiker")
  }
  
  func fetchAndMapNonExisting() {
    fetchBook(documentId: "does-not-exist")
  }
  
  func fetchAndTryMappingInvalidData() {
    fetchBook(documentId: "invalid-data")
  }
  
  private func fetchBook(documentId: String) {
    let docRef = db.collection("books").document(documentId)
    
    docRef.getDocument(as: Book.self) { result in
      switch result {
      case .success(let book):
        // A Book value was successfully initialized from the DocumentSnapshot.
        self.book = book
        self.errorMessage = nil
      case .failure(let error):
        // A Book value could not be initialized from the DocumentSnapshot.
        switch error {
        case DecodingError.typeMismatch(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.valueNotFound(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.keyNotFound(_, let context):
          self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
        case DecodingError.dataCorrupted(let key):
          self.errorMessage = "\(error.localizedDescription): \(key)"
        default:
          self.errorMessage = "Error decoding document: \(error.localizedDescription)"
        }
      }
    }
  }
}

Postępowanie w przypadku błędów w aktualizacjach na żywo

Poprzedni fragment kodu pokazuje, jak obsługiwać błędy przy pobieraniu jeden dokument. Oprócz jednorazowego pobierania danych Cloud Firestore obsługuje dostarczanie aktualizacji aplikacji w chwili ich wystąpienia przy użyciu tzw. zrzutów detektory: możemy zarejestrować detektor zrzutu w zbiorze (lub zapytaniu) oraz Cloud Firestore będzie wywoływać nasz detektor za każdym razem, gdy dostępna będzie aktualizacja.

Oto fragment kodu, który pokazuje, jak zarejestrować detektor zrzutu, dane mapy za pomocą Codable i eliminować ewentualne błędy. Dowiesz się z niego również, jak dodać nowy dokument do kolekcji. Nie trzeba aktualizować lokalnej tablicy, gdzie znajdują się zmapowane dokumenty, dzięki czemu dbamy o to, przez kod w detektorze zrzutu.

class MappingColorsViewModel: ObservableObject {
  @Published var colorEntries = [ColorEntry]()
  @Published var newColor = ColorEntry.empty
  @Published var errorMessage: String?
  
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
  
  public func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
  
  func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("colors")
        .addSnapshotListener { [weak self] (querySnapshot, error) in
          guard let documents = querySnapshot?.documents else {
            self?.errorMessage = "No documents in 'colors' collection"
            return
          }
          
          self?.colorEntries = documents.compactMap { queryDocumentSnapshot in
            let result = Result { try queryDocumentSnapshot.data(as: ColorEntry.self) }
            
            switch result {
            case .success(let colorEntry):
              if let colorEntry = colorEntry {
                // A ColorEntry value was successfully initialized from the DocumentSnapshot.
                self?.errorMessage = nil
                return colorEntry
              }
              else {
                // A nil value was successfully initialized from the DocumentSnapshot,
                // or the DocumentSnapshot was nil.
                self?.errorMessage = "Document doesn't exist."
                return nil
              }
            case .failure(let error):
              // A ColorEntry value could not be initialized from the DocumentSnapshot.
              switch error {
              case DecodingError.typeMismatch(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.valueNotFound(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.keyNotFound(_, let context):
                self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
              case DecodingError.dataCorrupted(let key):
                self?.errorMessage = "\(error.localizedDescription): \(key)"
              default:
                self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
              }
              return nil
            }
          }
        }
    }
  }
  
  func addColorEntry() {
    let collectionRef = db.collection("colors")
    do {
      let newDocReference = try collectionRef.addDocument(from: newColor)
      print("ColorEntry stored with new document reference: \(newDocReference)")
    }
    catch {
      print(error)
    }
  }
}

Wszystkie fragmenty kodu użyte w tym poście są częścią przykładowej aplikacji, które można pobrać z tego repozytorium GitHub.

Wypróbuj Codable.

Interfejs Swift Codable API zapewnia zaawansowany i elastyczny sposób mapowania danych zserializowane formaty do i z modelu danych aplikacji. W tym przewodniku Wiesz już, jak łatwo jest używać Cloud Firestore magazyn danych.

Zaczynając od podstawowego przykładu z prostymi typami danych, stopniowo co zwiększyło złożoność modelu danych, przy czym można było polegać za pomocą Codable i wdrożenia Firebase, aby wykonać mapowanie.

Więcej informacji o Codable znajdziesz w tych materiałach:

Dołożyliśmy wszelkich starań, aby opracować wyczerpujący przewodnik po mapach dokumentów Cloud Firestore; to nie jest wyczerpujące i być może używasz inne strategie mapowania typów. Za pomocą przycisku Prześlij opinię poniżej poinformować nas, jakich strategii używasz do mapowania innych rodzajów dane Cloud Firestore lub dane reprezentujące dane w Swift.

Nie ma powodu, aby nie korzystać z obsługi Codable w Cloud Firestore.