Сопоставление данных Cloud Firestore с помощью Swift Codable

API Codable, представленный в Swift 4, позволяет нам использовать возможности компилятора для упрощения преобразования данных из сериализованных форматов в типы Swift.

Возможно, вы использовали Codable для сопоставления данных из веб-API с моделью данных вашего приложения (и наоборот), но он гораздо более гибок.

В этом руководстве мы рассмотрим, как можно использовать Codable для сопоставления данных из Cloud Firestore с типами Swift и наоборот.

При извлечении документа из Cloud Firestore ваше приложение получит словарь пар ключ/значение (или массив словарей, если вы используете одну из операций, возвращающих несколько документов).

Конечно, вы можете продолжать напрямую использовать словари в Swift, и они обеспечивают большую гибкость, которая может быть именно тем, что нужно в вашем случае. Однако этот подход не является типобезопасным, и легко внести трудноотслеживаемые ошибки, допустив ошибки в названиях атрибутов или забыв сопоставить новый атрибут, добавленный вашей командой при выпуске этой потрясающей функции на прошлой неделе.

В прошлом многие разработчики обходили эти недостатки, реализовывая простой слой сопоставления, позволяющий сопоставлять словари с типами данных Swift. Но, опять же, большинство этих реализаций основано на ручном указании сопоставления между документами Cloud Firestore и соответствующими типами модели данных вашего приложения.

Благодаря поддержке Cloud Firestore API Codable от Swift это становится намного проще:

  • Вам больше не придется вручную реализовывать какой-либо код сопоставления.
  • Легко определить, как сопоставлять атрибуты с разными именами.
  • Имеет встроенную поддержку многих типов Swift.
  • И можно легко добавить поддержку сопоставления пользовательских типов.
  • А самое приятное: для простых моделей данных вам вообще не придется писать какой-либо код сопоставления.

Картографические данные

Cloud Firestore хранит данные в документах, сопоставляющих ключи со значениями. Чтобы извлечь данные из отдельного документа, можно вызвать метод DocumentSnapshot.data() , который возвращает словарь, сопоставляющий имена полей с типом Any : func data() -> [String : Any]? .

Это означает, что мы можем использовать синтаксис индексов Swift для доступа к каждому отдельному полю.

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

Хотя это может показаться простым и легким в реализации, этот код хрупкий, его сложно поддерживать и он подвержен ошибкам.

Как видите, мы делаем предположения о типах данных полей документа. Они могут быть верными, а могут и нет.

Помните, что, поскольку схемы нет, вы можете легко добавить новый документ в коллекцию и выбрать другой тип поля. Вы можете случайно выбрать строку для поля numberOfPages , что приведёт к труднодоступной проблеме сопоставления. Кроме того, вам придётся обновлять код сопоставления при каждом добавлении нового поля, что довольно громоздко.

И не будем забывать, что мы не пользуемся преимуществами строгой системы типов Swift, которая точно знает правильный тип для каждого свойства Book .

Что такое Codable?

Согласно документации Apple, Codable — это «тип, который может преобразовывать себя во внешнее представление и обратно». Фактически, Codable — это псевдоним типа для протоколов Encodable и Decodable. Соответствуя типу Swift этому протоколу, компилятор синтезирует код, необходимый для кодирования/декодирования экземпляра этого типа из сериализованного формата, например JSON.

Простой тип для хранения данных о книге может выглядеть так:

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

Как видите, соответствие типа стандарту Codable минимально инвазивно. Нам пришлось лишь добавить соответствие в протокол; никаких других изменений не потребовалось.

Теперь, когда все это реализовано, мы можем легко закодировать книгу в объект 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)")
}

Декодирование объекта JSON в экземпляр Book работает следующим образом:

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

Сопоставление простых типов в документах Cloud Firestore и обратно
используя Codable

Cloud Firestore поддерживает широкий набор типов данных, от простых строк до вложенных карт. Большинство из них напрямую соответствуют встроенным типам Swift. Давайте сначала рассмотрим сопоставление нескольких простых типов данных, прежде чем переходить к более сложным.

Чтобы сопоставить документы Cloud Firestore с типами Swift, выполните следующие действия:

  1. Убедитесь, что вы добавили фреймворк FirebaseFirestore в свой проект. Для этого можно использовать Swift Package Manager или CocoaPods .
  2. Импортируйте FirebaseFirestore в ваш файл Swift.
  3. Приведите свой тип в соответствие с Codable .
  4. (Необязательно, если вы хотите использовать тип в представлении « List ») Добавьте свойство id к вашему типу и используйте @DocumentID , чтобы Cloud Firestore сопоставил его с идентификатором документа. Мы рассмотрим это подробнее ниже.
  5. Используйте documentReference.data(as: ) для сопоставления ссылки на документ с типом Swift.
  6. Используйте documentReference.setData(from: ) для сопоставления данных из типов Swift с документом Cloud Firestore .
  7. (Необязательно, но настоятельно рекомендуется) Реализуйте правильную обработку ошибок.

Давайте обновим наш тип Book соответствующим образом:

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

Поскольку этот тип уже был кодируемым, нам оставалось только добавить свойство id и аннотировать его с помощью оболочки свойства @DocumentID .

Используя предыдущий фрагмент кода для извлечения и сопоставления документа, мы можем заменить весь код ручного сопоставления одной строкой:

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

Вы можете записать это ещё короче, указав тип документа при вызове getDocument(as:) . Это выполнит сопоставление и вернёт тип Result , содержащий сопоставленный документ, или ошибку в случае неудачного декодирования:

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

Обновление существующего документа так же просто, как вызов documentReference.setData(from: ) . Вот код для сохранения экземпляра 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)
    }
  }
}

При добавлении нового документа Cloud Firestore автоматически присваивает ему новый идентификатор. Это работает даже тогда, когда приложение находится в автономном режиме.

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

Помимо сопоставления простых типов данных, Cloud Firestore поддерживает ряд других типов данных, некоторые из которых являются структурированными типами, которые можно использовать для создания вложенных объектов внутри документа.

Вложенные пользовательские типы

Большинство атрибутов, которые мы хотим отобразить в наших документах, представляют собой простые значения, например, название книги или имя автора. Но что делать, если нам нужно сохранить более сложный объект? Например, нам может понадобиться хранить URL-адреса обложки книги в разных разрешениях.

Самый простой способ сделать это в Cloud Firestore — использовать карту:

Сохранение вложенного пользовательского типа в документе Firestore

При написании соответствующей структуры Swift мы можем воспользоваться тем фактом, что Cloud Firestore поддерживает URL-адреса — при сохранении поля, содержащего URL-адрес, он будет преобразован в строку и наоборот:

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

Обратите внимание, как мы определили структуру CoverImages для карты обложки в документе Cloud Firestore . Отметив свойство cover в BookWithCoverImages как необязательное, мы можем учитывать тот факт, что некоторые документы могут не содержать атрибута cover.

Если вам интересно, почему нет фрагмента кода для извлечения или обновления данных, вы будете рады услышать, что нет необходимости корректировать код для чтения или записи в/из Cloud Firestore : все это работает с кодом, который мы написали в начальном разделе.

Массивы

Иногда нам нужно хранить набор значений в документе. Жанры книг — хороший пример: например, « Автостопом по галактике» может относиться к нескольким категориям — в данном случае «Научная фантастика» и «Комедия»:

Хранение массива в документе Firestore

В Cloud Firestore это можно смоделировать с помощью массива значений. Поддерживается любой кодируемый тип данных (например, String , Int и т. д.). Ниже показано, как добавить массив жанров в нашу модель Book :

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

Поскольку это работает для любого кодируемого типа, мы можем использовать и пользовательские типы. Представьте, что мы хотим хранить список тегов для каждой книги. Помимо названия тега, мы хотим хранить и его цвет, например:

Хранение массива пользовательских типов в документе Firestore

Чтобы хранить теги таким образом, нам нужно всего лишь реализовать структуру Tag для представления тега и сделать ее кодируемой:

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

И точно так же мы можем хранить массив Tags в наших Book документах!

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

Несколько слов о сопоставлении идентификаторов документов

Прежде чем перейти к сопоставлению других типов, давайте немного поговорим о сопоставлении идентификаторов документов.

В некоторых предыдущих примерах мы использовали обёртку свойства @DocumentID для сопоставления идентификатора документа Cloud Firestore со свойством id наших типов Swift. Это важно по ряду причин:

  • Это помогает нам узнать, какой документ обновлять, если пользователь вносит локальные изменения.
  • Элементы List SwiftUI должны быть Identifiable , чтобы предотвратить смещение элементов при вставке.

Стоит отметить, что атрибут, помеченный как @DocumentID , не будет кодироваться кодировщиком Cloud Firestore при обратной записи документа. Это связано с тем, что идентификатор документа не является атрибутом самого документа, поэтому его запись в документ будет ошибкой.

При работе с вложенными типами (например, с массивом тегов Book в предыдущем примере этого руководства) добавлять свойство @DocumentID не требуется: вложенные свойства являются частью документа Cloud Firestore и не образуют отдельный документ. Следовательно, им не требуется идентификатор документа.

Даты и время

Cloud Firestore имеет встроенный тип данных для обработки дат и времени, а благодаря поддержке Codable в Cloud Firestore использовать их очень просто.

Давайте взглянем на этот документ, представляющий прародителя всех языков программирования — Ada, изобретенный в 1843 году:

Хранение дат в документе Firestore

Тип Swift для отображения этого документа может выглядеть так:

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

Мы не можем завершить этот раздел о датах и ​​времени, не обсудив @ServerTimestamp . Эта обёртка свойств — мощный инструмент для работы с временными метками в вашем приложении.

В любой распределённой системе существует вероятность, что часы отдельных систем не всегда полностью синхронизированы. Вы можете подумать, что это не такая уж большая проблема, но представьте себе последствия небольшой рассинхронизации часов для системы торговли акциями: даже отклонение в миллисекунду может привести к разнице в миллионы долларов при совершении сделки.

Cloud Firestore обрабатывает атрибуты, отмеченные @ServerTimestamp , следующим образом: если при сохранении атрибута (например, с помощью addDocument() ) он равен nil , Cloud Firestore заполнит поле текущей меткой времени сервера на момент записи в базу данных. Если при вызове addDocument() или updateData() значение поля не равно nil , Cloud Firestore оставит значение атрибута без изменений. Таким образом, можно легко реализовать такие поля, как createdAt и lastUpdatedAt .

Геоточки

Геолокации повсеместно используются в наших приложениях. Благодаря их сохранению становится возможным множество интересных функций. Например, может быть полезно сохранять местоположение задачи, чтобы приложение могло напомнить вам о ней, когда вы достигнете пункта назначения.

Cloud Firestore есть встроенный тип данных GeoPoint , который может хранить долготу и широту любого местоположения. Для сопоставления местоположений из документа Cloud Firestore и в него можно использовать тип GeoPoint :

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

Соответствующий тип в Swift — CLLocationCoordinate2D , и мы можем сопоставить эти два типа с помощью следующей операции:

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

Чтобы узнать больше о поиске документов по физическому местоположению, ознакомьтесь с этим руководством по решению .

Перечисления

Перечисления, пожалуй, одна из самых недооценённых возможностей языка Swift; они гораздо шире, чем кажется на первый взгляд. Распространённый вариант использования перечислений — моделирование дискретных состояний чего-либо. Например, мы можем написать приложение для управления статьями. Чтобы отслеживать статус статьи, можно использовать перечисление Status :

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

Cloud Firestore изначально не поддерживает перечисления (т.е. не может принудительно задать набор значений), но мы всё равно можем воспользоваться типизацией перечислений и выбрать кодируемый тип. В этом примере мы выбрали String , что означает, что все значения перечисления будут сопоставлены со строкой при хранении в документе Cloud Firestore .

А поскольку Swift поддерживает пользовательские необработанные значения, мы можем даже настроить, какие значения относятся к тому или иному перечислению. Например, если мы решили сохранить значение Status.inReview как «на рассмотрении», мы можем просто обновить перечисление выше следующим образом:

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

Настройка отображения

Иногда имена атрибутов документов Cloud Firestore , которые мы хотим сопоставить, не совпадают с именами свойств в нашей модели данных на Swift. Например, один из наших коллег, разработчик Python, решил использовать формат snake_case для всех имён атрибутов.

Не волнуйтесь: Codable о нас позаботится!

В подобных случаях можно использовать CodingKeys — перечисление, которое можно добавить к кодируемой структуре, чтобы указать, как будут отображаться определённые атрибуты.

Рассмотрим этот документ:

Документ Firestore с именем атрибута в формате snake_case

Чтобы сопоставить этот документ со структурой, имеющей свойство имени типа String , нам нужно добавить перечисление CodingKeys к структуре ProgrammingLanguage и указать имя атрибута в документе:

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

По умолчанию API Codable использует имена свойств наших типов Swift для определения имён атрибутов в документах Cloud Firestore , которые мы пытаемся сопоставить. Поэтому, если имена атрибутов совпадают, нет необходимости добавлять CodingKeys к нашим кодируемым типам. Однако, как только мы используем CodingKeys для конкретного типа, нам необходимо добавить все имена свойств, которые мы хотим сопоставить.

В приведённом выше фрагменте кода мы определили свойство id , которое может использоваться в качестве идентификатора в представлении List SwiftUI. Если бы мы не указали его в CodingKeys , оно не было бы сопоставлено при извлечении данных и, следовательно, стало бы равным nil . Это привело бы к заполнению представления List первым документом.

Любое свойство, не указанное как вариант в соответствующем перечислении CodingKeys , будет проигнорировано в процессе сопоставления. Это может быть удобно, если мы хотим специально исключить некоторые свойства из сопоставления.

Так, например, если мы хотим исключить свойство reasonWhyILoveThis из сопоставления, все, что нам нужно сделать, это удалить его из перечисления 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
  }
}

Иногда нам может потребоваться записать пустой атрибут обратно в документ Cloud Firestore . В Swift есть понятие необязательных значений (optionals), обозначающее отсутствие значения, и Cloud Firestore также поддерживает значения null . Однако по умолчанию при кодировании необязательных значений со значением nil они просто опускаются. @ExplicitNull даёт нам определённый контроль над обработкой необязательных значений Swift при их кодировании: отметив необязательное свойство как @ExplicitNull , мы можем указать Cloud Firestore записать это свойство в документ со значением NULL, если оно содержит значение nil .

Использование специального кодировщика и декодера для сопоставления цветов

В заключение нашего обзора сопоставления данных с помощью Codable рассмотрим пользовательские кодировщики и декодеры. В этом разделе не рассматривается собственный тип данных Cloud Firestore , но пользовательские кодировщики и декодеры широко используются в ваших приложениях Cloud Firestore .

«Как сопоставить цвета» — один из самых часто задаваемых вопросов разработчиков, не только для Cloud Firestore , но и для сопоставления между Swift и JSON. Существует множество решений, но большинство из них ориентированы на JSON, и почти все они сопоставляют цвета как вложенный словарь, состоящий из RGB-компонентов.

Кажется, должно быть решение получше и попроще. Почему бы нам не использовать веб-цвета (или, точнее, шестнадцатеричную нотацию цветов CSS) — они просты в использовании (по сути, это просто строка) и даже поддерживают прозрачность!

Чтобы иметь возможность сопоставить Swift Color с его шестнадцатеричным значением, нам нужно создать расширение Swift, которое добавляет Codable к 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)
  }

}

Используя decoder.singleValueContainer() , мы можем декодировать String в её Color эквивалент без необходимости вложения компонентов RGBA. Кроме того, вы можете использовать эти значения в веб-интерфейсе своего приложения без необходимости их предварительного преобразования!

Благодаря этому мы можем обновить код для сопоставления тегов, что упрощает прямую обработку цветов тегов вместо необходимости вручную сопоставлять их в коде пользовательского интерфейса нашего приложения:

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

Обработка ошибок

В приведенных выше фрагментах кода мы намеренно свели обработку ошибок к минимуму, но в рабочем приложении вам нужно будет обеспечить корректную обработку любых ошибок.

Вот фрагмент кода, показывающий, как обрабатывать любые ошибки, с которыми вы можете столкнуться:

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

Обработка ошибок в обновлениях в реальном времени

В предыдущем фрагменте кода показано, как обрабатывать ошибки при загрузке одного документа. Помимо однократной загрузки данных, Cloud Firestore также поддерживает доставку обновлений в ваше приложение по мере их появления, используя так называемые прослушиватели снимков: мы можем зарегистрировать прослушиватель снимков для коллекции (или запроса), и Cloud Firestore будет вызывать наш прослушиватель при каждом обновлении.

Вот фрагмент кода, показывающий, как зарегистрировать прослушиватель снимков, сопоставить данные с помощью Codable и обработать любые возможные ошибки. Он также показывает, как добавить новый документ в коллекцию. Как вы увидите, нет необходимости обновлять локальный массив, содержащий сопоставленные документы, самостоятельно, так как это делает код в прослушивателе снимков.

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

Все фрагменты кода, использованные в этой статье, являются частью примера приложения, которое вы можете загрузить из этого репозитория GitHub .

Идите вперед и используйте Codable!

API Codable в Swift предоставляет мощный и гибкий способ преобразования данных из сериализованных форматов в модель данных приложений и обратно. В этом руководстве вы увидели, насколько просто использовать его в приложениях, использующих Cloud Firestore в качестве хранилища данных.

Начав с базового примера с простыми типами данных, мы постепенно увеличивали сложность модели данных, при этом имея возможность полагаться на реализацию Codable и Firebase для выполнения сопоставления.

Для получения более подробной информации о Codable я рекомендую следующие ресурсы:

Хотя мы приложили все усилия, чтобы составить полное руководство по сопоставлению документов Cloud Firestore , оно не является исчерпывающим, и вы, возможно, используете другие стратегии для сопоставления своих типов. Используя кнопку «Отправить отзыв» ниже, расскажите нам, какие стратегии вы используете для сопоставления других типов данных Cloud Firestore или представления данных в Swift.

На самом деле нет никаких причин не использовать поддержку Codable в Cloud Firestore .