Swift Codable ile Cloud Firestore verilerini eşleme

Swift 4'te tanıtılan Swift'in Codable API'si, verileri serileştirilmiş formatlardan Swift türlerine eşlemeyi kolaylaştırmak için derleyicinin gücünden yararlanmamızı sağlar.

Verileri bir web API'sinden uygulamanızın veri modeline (ve tersi) eşlemek için Codable'ı kullanıyor olabilirsiniz, ancak bundan çok daha esnektir.

Bu kılavuzda, verileri Cloud Firestore'dan Swift türlerine (ve tersi) eşlemek için Codable'ın nasıl kullanılabileceğine bakacağız.

Cloud Firestore'dan bir belge alırken, uygulamanız anahtar/değer çiftlerinden oluşan bir sözlük (veya birden fazla belge döndüren işlemlerden birini kullanıyorsanız bir dizi sözlük) alacaktır.

Artık Swift'deki sözlükleri doğrudan kullanmaya kesinlikle devam edebilirsiniz ve bunlar, kullanım durumunuzun tam olarak gerektirdiği gibi büyük bir esneklik sunar. Bununla birlikte, bu yaklaşım yazım açısından güvenli değildir ve özellik adlarını yanlış yazarak veya ekibinizin geçen hafta bu heyecan verici yeni özelliği piyasaya sürdüklerinde eklediği yeni özelliği haritalamayı unutarak bulunması zor hatalara neden olmak kolaydır.

Geçmişte pek çok geliştirici, sözlükleri Swift türleriyle eşlemelerine olanak tanıyan basit bir eşleme katmanı uygulayarak bu eksiklikleri gidermeye çalıştı. Ancak yine de bu uygulamaların çoğu, Cloud Firestore belgeleri ile uygulamanızın veri modelinin karşılık gelen türleri arasındaki eşlemenin manuel olarak belirlenmesine dayanmaktadır.

Cloud Firestore'un Swift'in Codable API'sine verdiği destekle bu çok daha kolay hale geliyor:

  • Artık herhangi bir eşleme kodunu manuel olarak uygulamanıza gerek kalmayacak.
  • Niteliklerin farklı adlarla nasıl eşleneceğini tanımlamak kolaydır.
  • Swift'in birçok türü için yerleşik desteğe sahiptir.
  • Özel türlerin eşlenmesine yönelik destek eklemek de kolaydır.
  • Hepsinden iyisi: basit veri modelleri için herhangi bir eşleme kodu yazmanıza gerek kalmayacak.

Verileri eşleme

Cloud Firestore, verileri anahtarlarla değerlerle eşleyen belgelerde saklar. Tek bir belgeden veri almak için, alan adlarını Any ile eşleyen bir sözlük döndüren DocumentSnapshot.data() öğesini çağırabiliriz: func data() -> [String : Any]? .

Bu, her bir alana erişmek için Swift'in alt simge sözdizimini kullanabileceğimiz anlamına gelir.

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

Uygulaması basit ve kolay görünse de bu kod hassastır, bakımı zordur ve hataya açıktır.

Gördüğünüz gibi belge alanlarının veri türleri hakkında varsayımlarda bulunuyoruz. Bunlar doğru olabilir de olmayabilir de.

Unutmayın, şema olmadığı için koleksiyona kolayca yeni bir belge ekleyebilir ve bir alan için farklı bir tür seçebilirsiniz. numberOfPages alanı için yanlışlıkla dizeyi seçebilirsiniz; bu da bulunması zor bir eşleme sorununa yol açabilir. Ayrıca, yeni bir alan eklendiğinde eşleme kodunuzu güncellemeniz gerekecektir ki bu oldukça zahmetlidir.

Ve Book her özelliği için doğru türü tam olarak bilen Swift'in güçlü tür sisteminden faydalanmadığımızı da unutmayalım.

Kodlanabilir nedir bu arada?

Apple'ın belgelerine göre Codable, "kendisini harici bir temsile dönüştürebilen ve bu temsilden çıkabilen bir türdür." Aslında Codable, Encodable ve Decodable protokollerinin tür takma adıdır. Derleyici, Swift tipini bu protokole uygun hale getirerek, bu tipteki bir örneği JSON gibi serileştirilmiş bir formattan kodlamak/kodunu çözmek için gereken kodu sentezleyecektir.

Bir kitapla ilgili verileri depolamak için basit bir tür şöyle görünebilir:

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

Gördüğünüz gibi türün Codable'a uygun hale getirilmesi minimal düzeyde invaziftir. Protokole yalnızca uygunluğu eklememiz gerekiyordu; başka hiçbir değişikliğe gerek yoktu.

Bunu yerine getirdiğimizde artık bir kitabı kolayca bir JSON nesnesine kodlayabiliriz:

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

Bir JSON nesnesinin Book örneğine dönüştürülmesi şu şekilde çalışır:

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

Cloud Firestore belgelerindeki basit türlerle eşleme
Codable'ı kullanma

Cloud Firestore, basit dizelerden iç içe haritalara kadar geniş bir veri türü kümesini destekler. Bunların çoğu doğrudan Swift'in yerleşik türlerine karşılık gelir. Daha karmaşık olanlara dalmadan önce, ilk olarak bazı basit veri türlerinin eşlenmesine bir göz atalım.

Cloud Firestore belgelerini Swift türleriyle eşlemek için şu adımları izleyin:

  1. FirebaseFirestore çerçevesini projenize eklediğinizden emin olun. Bunu yapmak için Swift Paket Yöneticisini veya CocoaPod'ları kullanabilirsiniz.
  2. FirebaseFirestore Swift dosyanıza aktarın.
  3. Türünüzü Codable uygun hale getirin.
  4. (Türü List görünümünde kullanmak istiyorsanız isteğe bağlıdır) Türünüze bir id özelliği ekleyin ve @DocumentID kullanarak Cloud Firestore'a bunu belge kimliğiyle eşlemesini söyleyin. Bunu aşağıda daha ayrıntılı olarak ele alacağız.
  5. Bir belge referansını Swift türüne eşlemek için documentReference.data(as: ) kullanın.
  6. Swift türlerindeki verileri bir Cloud Firestore belgesine eşlemek için documentReference.setData(from: ) öğesini kullanın.
  7. (İsteğe bağlı, ancak önemle tavsiye edilir) Doğru hata işlemeyi uygulayın.

Book türümüzü buna göre güncelleyelim:

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

Bu tür zaten kodlanabilir olduğundan, yalnızca id özelliğini eklememiz ve ona @DocumentID özellik sarmalayıcısıyla açıklama eklememiz gerekiyordu.

Bir belgeyi getirmek ve eşlemek için önceki kod pasajını alarak, tüm manuel eşleme kodunu tek bir satırla değiştirebiliriz:

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:) işlevini çağırırken belgenin türünü belirterek bunu daha da kısa bir şekilde yazabilirsiniz. Bu, eşlemeyi sizin için gerçekleştirecek ve eşlenen belgeyi içeren bir Result türü veya kod çözmenin başarısız olması durumunda bir hata döndürecektir:

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

Mevcut bir belgeyi güncellemek documentReference.setData(from: ) çağırmak kadar basittir. Bazı temel hata yönetimini de içeren bir Book örneğini kaydetmeye yönelik kod aşağıda verilmiştir:

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

Yeni bir belge eklerken Cloud Firestore, belgeye yeni bir belge kimliği atama işlemini otomatik olarak üstlenir. Bu, uygulama şu anda çevrimdışı olduğunda bile çalışır.

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, basit veri türlerini eşlemenin yanı sıra, bir belge içinde iç içe geçmiş nesneler oluşturmak için kullanabileceğiniz yapılandırılmış türler olan bir dizi başka veri türünü de destekler.

İç içe özel türler

Belgelerimizde eşlemek istediğimiz özelliklerin çoğu, kitabın başlığı veya yazarın adı gibi basit değerlerdir. Peki ya daha karmaşık bir nesneyi saklamamız gereken durumlar? Örneğin, kitabın kapağının URL'lerini farklı çözünürlüklerde saklamak isteyebiliriz.

Bunu Cloud Firestore'da yapmanın en kolay yolu bir harita kullanmaktır:

Yuvalanmış bir özel türü Firestore belgesinde saklama

İlgili Swift yapısını yazarken Cloud Firestore'un URL'leri desteklemesi gerçeğinden yararlanabiliriz; URL içeren bir alanı saklarken, bu bir dizeye dönüştürülür ve bunun tersi de geçerlidir:

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

Cloud Firestore belgesindeki kapak haritası için CoverImages adlı yapıyı nasıl tanımladığımıza dikkat edin. BookWithCoverImages kapak özelliğini isteğe bağlı olarak işaretleyerek, bazı belgelerin kapak özelliği içermeyebileceği gerçeğinin üstesinden gelebiliriz.

Verileri almak veya güncellemek için neden bir kod parçacığının olmadığını merak ediyorsanız, Cloud Firestore'dan okumak veya Cloud Firestore'a yazmak için kodu ayarlamanıza gerek olmadığını duymaktan memnuniyet duyacaksınız: bunların tümü bizim verdiğimiz kodla çalışır. İlk bölümde yazdım.

Diziler

Bazen bir değerler koleksiyonunu bir belgede saklamak isteriz. Bir kitabın türleri buna iyi bir örnektir: Otostopçunun Galaksi Rehberi gibi bir kitap birkaç kategoriye ayrılabilir - bu durumda "Bilim Kurgu" ve "Komedi":

Bir diziyi Firestore belgesinde saklama

Cloud Firestore'da bunu bir dizi değer kullanarak modelleyebiliriz. Bu, herhangi bir kodlanabilir tür için desteklenir ( String , Int vb. gibi). Aşağıda Book modelimize bir tür dizisinin nasıl ekleneceği gösterilmektedir:

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

Bu herhangi bir kodlanabilir tür için işe yaradığından özel türleri de kullanabiliriz. Her kitap için bir etiket listesi depolamak istediğimizi düşünün. Etiketin adının yanı sıra etiketin rengini de şu şekilde saklamak istiyoruz:

Bir Firestore belgesinde bir dizi özel türü depolama

Etiketleri bu şekilde saklamak için tek yapmamız gereken, bir etiketi temsil edecek ve onu kodlanabilir hale getirecek bir Tag yapısını uygulamaktır:

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

Ve bunun gibi, Book belgelerimizde bir dizi Tags saklayabiliriz!

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

Belge kimliklerini eşleme hakkında kısa bir bilgi

Daha fazla türü eşlemeye geçmeden önce, bir an için belge kimliklerini eşleme hakkında konuşalım.

Cloud Firestore belgelerimizin belge kimliğini Swift türlerimizin id özelliğiyle eşlemek için önceki örneklerin bazılarında @DocumentID özellik sarmalayıcısını kullandık. Bu birkaç nedenden dolayı önemlidir:

  • Kullanıcının yerel değişiklik yapması durumunda hangi belgenin güncelleneceğini bilmemize yardımcı olur.
  • SwiftUI'nin List öğelerin eklendiklerinde etrafa sıçramasını önlemek için öğelerinin Identifiable olmasını gerektirir.

@DocumentID olarak işaretlenen bir özelliğin, belgeyi geri yazarken Cloud Firestore'un kodlayıcısı tarafından kodlanmayacağını belirtmekte fayda var. Bunun nedeni, belge kimliğinin belgenin kendisinin bir niteliği olmamasıdır; dolayısıyla onu belgeye yazmak bir hata olur.

Yuvalanmış türlerle çalışırken (bu kılavuzdaki daha önceki bir örnekte Book etiket dizisi gibi), @DocumentID özelliği eklemenize gerek yoktur: yuvalanmış özellikler Cloud Firestore belgesinin bir parçasıdır ve bir belge oluşturmaz. ayrı bir belge. Bu nedenle belge kimliğine ihtiyaç duymazlar.

Tarihler ve saatler

Cloud Firestore, tarih ve saatleri işlemek için yerleşik bir veri türüne sahiptir ve Cloud Firestore'un Codable desteği sayesinde bunları kullanmak kolaydır.

Tüm programlama dillerinin atası olan Ada'nın 1843 yılında icat ettiği bu belgeye bir göz atalım:

Tarihleri ​​bir Firestore belgesinde saklama

Bu belgeyi eşlemek için kullanılan Swift türü şöyle görünebilir:

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

@ServerTimestamp hakkında konuşmadan tarih ve saatlerle ilgili bu bölümden çıkamayız. Bu özellik sarmalayıcı, uygulamanızdaki zaman damgalarıyla baş etme konusunda çok güçlü bir araçtır.

Herhangi bir dağıtılmış sistemde, bireysel sistemlerdeki saatlerin her zaman tamamen senkronize olmaması ihtimali vardır. Bunun çok da önemli olmadığını düşünebilirsiniz, ancak bir saatin hisse senedi alım satım sistemi için biraz senkronize çalışmasının sonuçlarını hayal edin: bir milisaniyelik sapma bile, bir alım satım gerçekleştirirken milyonlarca dolarlık bir farkla sonuçlanabilir.

Cloud Firestore, @ServerTimestamp ile işaretlenmiş öznitelikleri şu şekilde işler: özniteliği sakladığınızda (örneğin, addDocument() kullanarak) nil ise, Cloud Firestore, alanı veritabanına yazarken geçerli sunucu zaman damgasıyla doldurur. . addDocument() veya updateData() öğesini çağırdığınızda alan nil değilse, Cloud Firestore öznitelik değerine dokunulmadan kalacaktır. Bu şekilde createdAt ve lastUpdatedAt gibi alanları uygulamak kolaydır.

Coğrafi noktalar

Coğrafi konumlar uygulamalarımızın her yerinde mevcuttur. Pek çok heyecan verici özellik, bunların saklanmasıyla mümkün hale gelir. Örneğin, bir hedefe ulaştığınızda uygulamanızın size bir görevi hatırlatabilmesi için bir görevin konumunu saklamak yararlı olabilir.

Cloud Firestore, herhangi bir konumun enlem ve boylamını saklayabilen yerleşik bir veri türü olan GeoPoint sahiptir. Bir Cloud Firestore belgesindeki/bulunan konumları eşlemek için GeoPoint türünü kullanabiliriz:

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

Swift'de buna karşılık gelen tür CLLocationCoordinate2D ve bu iki tür arasında aşağıdaki işlemle eşleme yapabiliriz:

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

Belgeleri fiziksel konuma göre sorgulama hakkında daha fazla bilgi edinmek için bu çözüm kılavuzuna göz atın.

Numaralandırmalar

Numaralandırmalar muhtemelen Swift'deki en az önemsenen dil özelliklerinden biridir; onlarda göründüğünden çok daha fazlası var. Numaralandırmaların yaygın bir kullanım durumu, bir şeyin ayrık durumlarını modellemektir. Örneğin makaleleri yönetmek için bir uygulama yazıyor olabiliriz. Bir makalenin durumunu izlemek için bir enum Status kullanmak isteyebiliriz:

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

Cloud Firestore, numaralandırmaları yerel olarak desteklemez (yani, değerler kümesini uygulayamaz), ancak yine de numaralandırmaların yazılabildiği gerçeğinden yararlanabilir ve kodlanabilir bir tür seçebiliriz. Bu örnekte, String seçtik; bu, bir Cloud Firestore belgesinde depolandığında tüm numaralandırma değerlerinin dizeyle/dizeden eşleneceği anlamına gelir.

Swift özel ham değerleri desteklediğinden, hangi değerlerin hangi numaralandırma durumuna karşılık geldiğini bile özelleştirebiliriz. Örneğin, Status.inReview durumunu "inceleniyor" olarak saklamaya karar verirsek yukarıdaki numaralandırmayı şu şekilde güncelleyebiliriz:

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

Eşlemeyi özelleştirme

Bazen eşlemek istediğimiz Cloud Firestore belgelerinin öznitelik adları, Swift'deki veri modelimizdeki özelliklerin adlarıyla eşleşmiyor. Örneğin, iş arkadaşlarımızdan biri bir Python geliştiricisi olabilir ve tüm özellik adları için yılan_case'i seçmeye karar verebilir.

Endişelenmeyin: Codable bizi koruyor!

Bu gibi durumlarda CodingKeys faydalanabiliriz. Bu, belirli niteliklerin nasıl eşleneceğini belirtmek için kodlanabilir bir yapıya ekleyebileceğimiz bir numaralandırmadır.

Bu belgeyi düşünün:

Snake_cased öznitelik adına sahip bir Firestore belgesi

Bu belgeyi String türünde bir name özelliğine sahip bir yapıyla eşlemek için, ProgrammingLanguage yapısına bir CodingKeys numaralandırması eklememiz ve belgedeki niteliğin adını belirtmemiz gerekir:

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

Varsayılan olarak Codable API, eşlemeye çalıştığımız Cloud Firestore belgelerindeki nitelik adlarını belirlemek için Swift türlerimizin özellik adlarını kullanacaktır. Yani öznitelik adları eşleştiği sürece kodlanabilir türlerimize CodingKeys eklemeye gerek yoktur. Ancak CodingKeys belirli bir tür için kullandığımızda, eşlemek istediğimiz tüm özellik adlarını eklememiz gerekir.

Yukarıdaki kod parçacığında, SwiftUI List görünümünde tanımlayıcı olarak kullanmak isteyebileceğimiz bir id özelliği tanımladık. Eğer bunu CodingKeys belirtmeseydik veri alınırken haritalanmazdı ve dolayısıyla nil olurdu. Bu, List görünümünün ilk belgeyle doldurulmasına neden olur.

İlgili CodingKeys numaralandırmasında durum olarak listelenmeyen herhangi bir özellik, eşleme işlemi sırasında göz ardı edilecektir. Bazı özelliklerin eşleştirilmesinin özellikle hariç tutulmasını istiyorsak bu aslında kullanışlı olabilir.

Örneğin, Reason reasonWhyILoveThis özelliğini eşlenmekten hariç tutmak istiyorsak, tek yapmamız gereken onu CodingKeys numaralandırmasından kaldırmaktır:

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

Bazen Cloud Firestore belgesine boş bir özelliği tekrar yazmak isteyebiliriz. Swift, bir değerin yokluğunu belirtmek için seçenekler kavramına sahiptir ve Cloud Firestore da null değerleri destekler. Ancak, nil değeri olan kodlama seçeneklerinin varsayılan davranışı, bunların atlanmasıdır. @ExplicitNull Swift seçeneklerinin kodlanırken nasıl ele alınacağı konusunda bize biraz kontrol sağlıyor: isteğe bağlı bir özelliği @ExplicitNull olarak işaretleyerek, Cloud Firestore'a, nil değeri içeriyorsa bu özelliği boş bir değerle belgeye yazmasını söyleyebiliriz.

Renkleri eşlemek için özel bir kodlayıcı ve kod çözücü kullanma

Codable ile veri eşleme kapsamımızın son konusu olarak, özel kodlayıcıları ve kod çözücüleri tanıtalım. Bu bölüm yerel Cloud Firestore veri türünü kapsamaz ancak özel kodlayıcılar ve kod çözücüler Cloud Firestore uygulamalarınızda oldukça kullanışlıdır.

"Renkleri nasıl eşleyebilirim" yalnızca Cloud Firestore için değil, aynı zamanda Swift ile JSON arasındaki eşleme için de en sık sorulan geliştirici sorularından biridir. Piyasada çok sayıda çözüm var, ancak bunların çoğu JSON'a odaklanıyor ve hemen hemen hepsi renkleri, RGB bileşenlerinden oluşan iç içe geçmiş bir sözlük olarak eşliyor.

Görünüşe göre daha iyi ve daha basit bir çözüm olmalı. Neden web renklerini (veya daha spesifik olmak gerekirse, CSS onaltılık renk gösterimini) kullanmıyoruz? Kullanımı kolaydır (temel olarak yalnızca bir dize) ve hatta şeffaflığı bile destekliyorlar!

Swift Color hex değerine eşleyebilmek için Codable'ı Color ekleyen bir Swift uzantısı oluşturmamız gerekiyor.

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() işlevini kullanarak, RGBA bileşenlerini iç içe yerleştirmeye gerek kalmadan bir String Color eşdeğerinin kodunu çözebiliriz. Ayrıca bu değerleri, önce dönüştürmenize gerek kalmadan uygulamanızın web kullanıcı arayüzünde kullanabilirsiniz!

Bununla, etiketlerin eşlenmesine ilişkin kodu güncelleyebiliriz, böylece etiket renklerini uygulamamızın kullanıcı arayüzü kodunda manuel olarak eşlemek yerine doğrudan kullanmayı kolaylaştırabiliriz:

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

Hataları işleme

Yukarıdaki kod parçacıklarında hata işlemeyi kasıtlı olarak minimumda tuttuk, ancak bir üretim uygulamasında hataların incelikli bir şekilde ele alındığından emin olmak isteyeceksiniz.

Karşılaşabileceğiniz hata durumlarını nasıl ele alacağınızı gösteren bir kod pasajını burada bulabilirsiniz:

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

Canlı güncellemelerdeki hataları işleme

Önceki kod parçacığı, tek bir belge getirilirken hataların nasıl ele alınacağını gösterir. Cloud Firestore, verileri bir kez getirmenin yanı sıra, anlık görüntü dinleyicileri olarak adlandırılan uygulamaları kullanarak uygulamanıza güncellemeleri anında iletmeyi de destekler: bir koleksiyona (veya sorguya) bir anlık görüntü dinleyicisi kaydedebiliriz ve Cloud Firestore, dinleyicimizi orada olduğunda arayacaktır. bir güncellemedir.

Burada anlık görüntü dinleyicisinin nasıl kaydedileceğini, Codable kullanılarak verilerin nasıl eşleneceğini ve oluşabilecek hataların nasıl ele alınacağını gösteren bir kod pasajı bulunmaktadır. Ayrıca koleksiyona nasıl yeni bir belge ekleneceğini de gösterir. Göreceğiniz gibi, eşlenen belgeleri tutan yerel diziyi kendimiz güncellememize gerek yok çünkü bu, anlık görüntü dinleyicisindeki kod tarafından hallediliyor.

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

Bu gönderide kullanılan tüm kod parçacıkları , bu GitHub deposundan indirebileceğiniz örnek uygulamanın parçasıdır.

Devam edin ve Codable'ı kullanın!

Swift'in Codable API'si, serileştirilmiş formatlardaki verileri uygulamanızın veri modeliyle eşleştirmek için güçlü ve esnek bir yol sağlar. Bu kılavuzda, Cloud Firestore'u veri deposu olarak kullanan uygulamalarda kullanımının ne kadar kolay olduğunu gördünüz.

Basit veri türlerine sahip temel bir örnekten başlayarak, veri modelinin karmaşıklığını giderek artırdık ve bu arada eşlemeyi bizim için gerçekleştirme konusunda Codable ve Firebase'in uygulamasına güvenebildik.

Codable hakkında daha fazla ayrıntı için aşağıdaki kaynakları öneririm:

Cloud Firestore belgelerini eşlemeye yönelik kapsamlı bir kılavuz derlemek için elimizden gelenin en iyisini yapmış olsak da, bu kapsamlı değildir ve türlerinizi eşlemek için başka stratejiler kullanıyor olabilirsiniz. Aşağıdaki Geri bildirim gönder düğmesini kullanarak, diğer Cloud Firestore veri türlerini eşlemek veya verileri Swift'de temsil etmek için hangi stratejileri kullandığınızı bize bildirin.

Cloud Firestore'un Codable desteğini kullanmamak için gerçekten hiçbir neden yok.