Cloud Firestore verilerini Swift Codable ile eşleme

Swift 4'te kullanıma sunulan Swift Codable API, serileştirilmiş biçimlerdeki verileri Swift türleriyle eşlemeyi kolaylaştırmak için derleyicinin gücünden yararlanmamızı sağlar.

Codable'ı, bir web API'sindeki verileri uygulamanızın veri modeliyle eşlemek için kullanıyor olabilirsiniz (veya tam tersi). Ancak Codable bundan çok daha esnektir.

Bu kılavuzda, Codable'ın Cloud Firestore'deki verileri Swift türlerine (veya tam tersi) eşlemek için nasıl kullanılabileceğini inceleyeceğiz.

Cloud Firestore kaynağından bir doküman getirirken uygulamanız bir anahtar/değer çiftleri sözlüğü (veya birden fazla doküman döndüren işlemlerden birini kullanıyorsanız bir sözlük dizisi) alır.

Artık Swift'teki sözlükleri doğrudan kullanmaya devam edebilirsiniz. Üstelik bunlar, kullanım alanınızın tam olarak ihtiyaç duyabileceği kadar esnek bir esneklik sunar. Ancak bu yaklaşım tür açısından güvenli değildir ve özellik adlarını yanlış yazarak veya ekibinizin geçen hafta heyecan verici yeni özelliği kullanıma sunarken eklediği yeni özelliği eşlemeyi unutarak bulunması zor hatalar oluşturmak kolaydır.

Geçmişte birçok geliştirici, sözlükleri Swift türleriyle eşlemelerine olanak tanıyan basit bir eşleme katmanı uygulayarak bu eksiklikleri gideriyordu. Ancak yine de bu uygulamaların çoğu, Cloud Firestore dokümanları ile uygulamanızın veri modelindeki ilgili türler arasındaki eşlemeyi manuel olarak belirtmeye dayanır.

Cloud Firestore'ın Swift'in Codable API'sini desteklemesi sayesinde bu işlem çok daha kolay hale geliyor:

  • Artık eşleme kodunu manuel olarak uygulamanız gerekmeyecek.
  • Farklı adlara sahip özelliklerin nasıl eşleneceğini tanımlamak kolaydır.
  • Çoğu Swift türü için yerleşik desteğe sahiptir.
  • Özel türlerin eşlenmesi için destek eklemek de kolaydır.
  • Üstelik, basit veri modelleri için herhangi bir eşleme kodu yazmanıza gerek kalmaz.

Verileri eşleme

Cloud Firestore, verileri anahtarları değerlerle eşleyen belgelerde depolar. Bir dokümandan veri almak için DocumentSnapshot.data() işlevini çağırabiliriz. Bu işlev, alan adlarını bir Any ile eşleyen bir sözlük döndürür: func data() -> [String : Any]?.

Bu sayede, her bir alana erişmek için Swift'in alt dize söz dizimini kullanabiliriz.

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

Basit ve uygulanması kolay gibi görünse de bu kod hassastır, bakımı zordur ve hatalara açıktır.

Gördüğünüz gibi, belge alanlarının veri türleri hakkında varsayımlar yapıyoruz. Bu bilgiler doğru veya yanlış olabilir.

Şema olmadığından koleksiyona kolayca yeni bir doküman ekleyebileceğinizi ve bir alan için farklı bir tür seçebileceğinizi unutmayın. numberOfPages alanı için yanlışlıkla dize seçebilirsiniz. Bu, bulunması zor bir eşleme sorununa neden olur. Ayrıca, yeni bir alan eklendiğinde eşleme kodunuzu güncellemeniz gerekir. Bu da oldukça zahmetli bir işlemdir.

Ayrıca, Book öğesinin her bir özelliği için doğru türü tam olarak bilen Swift'in güçlü tür sisteminden yararlanmadığımızı da unutmayın.

Kodlanabilir nedir?

Apple'ın dokümanları uyarınca, Kodlanabilir "kendisini harici bir temsile dönüştürebilen ve harici bir temsilden dönüştürülebilen bir türdür." Aslında Codable, Encodable ve Decodable protokolleri için bir tür takma adıdır. Bir Swift türünü bu protokole uygun hale getirerek derleyici, bu türün bir örneğini JSON gibi serileştirilmiş bir biçimden kodlamak/kodunu çözmek için gereken kodu sentezleyecektir.

Bir kitapla ilgili verileri depolamak için kullanılan basit bir tür şu şekilde görünebilir:

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

Gördüğünüz gibi, türü Kodlanabilir'e uyarlamak çok az rahatsız edici bir işlemdir. Yalnızca protokole uygunluğu eklememiz gerekiyordu. Başka bir değişiklik gerekmiyordu.

Bu işlemden sonra bir kitabı JSON nesnesine kolayca 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 kodunu Book örneğine dönüştürme işlemi aşağıdaki gibi çalışır:

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

Codable'ı kullanarak Cloud Firestore dokümanlarındaki
basit türlerle eşleme yapma

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

Cloud Firestore belgelerini Swift türleriyle eşlemek için aşağıdaki adımları uygulayın:

  1. Projenize FirebaseFirestore çerçevesini eklediğinizden emin olun. Bunu yapmak için Swift Package Manager veya CocoaPods'u kullanabilirsiniz.
  2. FirebaseFirestore dosyasını Swift dosyanıza aktarın.
  3. Türünüzü Codable'e uygun hale getirin.
  4. (İsteğe bağlı, türü List görünümünde kullanmak istiyorsanız) Türünüze bir id mülkü ekleyin ve Cloud Firestore'e bunu doküman kimliğiyle eşlemesini söylemek için @DocumentID kullanın. Bu konuyu aşağıda daha ayrıntılı olarak ele alacağız.
  5. Bir doküman referansını Swift türüyle eşlemek için documentReference.data(as: ) simgesini kullanın.
  6. Swift türlerindeki verileri bir Cloud Firestore belgesiyle eşlemek için documentReference.setData(from: ) kullanın.
  7. (İsteğe bağlıdır, ancak kesinlikle önerilir) Doğru hata işleme yöntemleri uygulayın.

Book türünü 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 mülkünü eklememiz ve @DocumentID mülk sarmalayıcısıyla ek açıklama eklememiz gerekiyordu.

Bir belgeyi getirme ve eşlemeyle ilgili önceki kod snippet'ini ele alırsak 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 yazabilirsiniz. Bu işlem, eşlemeyi sizin için gerçekleştirir ve eşlenen dokümanı içeren bir Result türü döndürür veya kod çözme başarısız olursa bir hata döndürür:

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 dokümanı güncellemek için documentReference.setData(from: ) işlevini çağırmanız yeterlidir. Bazı temel hata işleme işlemleri de dahil olmak üzere Book örneğini kaydetmek için gereken 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 eklediğinizde Cloud Firestore, belgeye yeni bir belge kimliği atamayı otomatik olarak yapar. Bu işlem, uygulama şu anda çevrimdışı olsa 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şlemeye ek olarak bir dizi başka veri türünü de destekler. Bunlardan bazıları, bir belge içinde iç içe yerleştirilmiş nesneler oluşturmak için kullanabileceğiniz yapılandırılmış türlerdir.

İç içe yerleştirilmiş özel türler

Belgelerimizde eşlemek istediğimiz özelliklerin çoğu, kitabın başlığı veya yazarın adı gibi basit değerlerdir. Peki daha karmaşık bir nesneyi depolamamız gerektiğinde ne yapmalıyız? Örneğin, kitabın kapağının URL'lerini farklı çözünürlüklerde depolamak isteyebiliriz.

Cloud Firestore ürününde bunu yapmanın en kolay yolu harita kullanmaktır:

İç içe yerleştirilmiş özel bir türü Firestore dokümanında depolama

İlgili Swift yapısını yazarken Cloud Firestore'ün URL'leri desteklediği gerçeğinden yararlanabiliriz. URL içeren bir alan depolanırken dize biçimine 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 dokümanında kapak haritası için nasıl bir CoverImages yapısı tanımladığımıza dikkat edin. BookWithCoverImages üzerindeki kapak mülkünü isteğe bağlı olarak işaretleyerek bazı dokümanların kapak özelliği içermeyebileceğini hesaba katabiliriz.

Verileri getirme veya güncellemeyle ilgili kod snippet'inin neden olmadığını merak ediyorsanız Cloud Firestore kaynağından veri okumak veya yazmak için kodu ayarlamanıza gerek olmadığını duymaktan memnuniyet duyacaksınız: Tüm bunlar, ilk bölümde yazdığımız kodla çalışır.

Diziler

Bazen bir dokümanda değer koleksiyonu depolamak isteriz. Bir kitabın türleri buna iyi bir örnektir: Ulaşımcının Galaksi Rehberi gibi bir kitap, bu örnekte "Bilim Kurgu" ve "Komedi" olmak üzere birkaç kategoriye ayrılabilir:

Bir diziyi Firestore belgesinde depolama

Cloud Firestore'te bunu bir değer dizisi kullanarak modelleyebiliriz. Bu, tüm kodlanabilir türler (String, Int vb.) için desteklenir. 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, kodlanabilir her tür için işe yaradığından özel türleri de kullanabiliriz. Her kitap için bir etiket listesi depolamak istediğimizi varsayalım. Etiketin adının yanı sıra rengini de şu şekilde saklamak istiyoruz:

Bir Firestore dokümanında özel tür dizisi depolama

Etiketleri bu şekilde depolamak için tüm yapmanız gereken, bir etiketi temsil eden Tag struct uygulamak ve kodlanabilir hale getirmektir:

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

Bu şekilde, Book dokümanlarımızda bir Tags dizisi depolayabiliriz.

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

Doküman kimliklerini eşleme hakkında kısa bir hatırlatma

Daha fazla türün eşlenmesine geçmeden önce doküman kimliklerinin eşlenmesi hakkında biraz konuşalım.

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

  • Kullanıcı yerel değişiklikler yaptığında hangi dokümanın güncelleneceğini bilmemize yardımcı olur.
  • SwiftUI'nın List, öğelerin eklendiğinde etrafta zıplamasını önlemek için öğelerinin Identifiable olmasını gerektirir.

@DocumentID olarak işaretlenen bir özelliğin, doküman geri yazılırken Cloud Firestore'un kodlayıcısı tarafından kodlanmayacağını belirtmek gerekir. Bunun nedeni, doküman kimliğinin dokümanın bir özelliği olmamasıdır. Bu nedenle, doküman kimliğini dokümana yazmak hata olur.

İç içe yerleştirilmiş türlerle çalışırken (bu kılavuzun önceki bir örneğindeki Book öğesindeki etiket dizisi gibi) @DocumentID özelliği eklemeniz gerekmez: İç içe yerleştirilmiş özellikler Cloud Firestore belgesinin bir parçasıdır ve ayrı bir belge oluşturmaz. Bu nedenle, belge kimliğine ihtiyaçları yoktur.

Tarihler ve saatler

Cloud Firestore, tarih ve saatleri işlemek için yerleşik bir veri türüne sahiptir. Cloud Firestore'ın Kodlanabilir'i desteklemesi sayesinde bu tür verileri kullanmak kolaydır.

Tüm programlama dillerinin anası olan Ada'yı (1843'te icat edilmiştir) temsil eden bu belgeye göz atalım:

Firestore dokümanlarında tarih depolama

Bu dokümanın eşlenmesi için Swift türü şu şekilde görünebilir:

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

Tarih ve saatlerle ilgili bu bölümden @ServerTimestamp hakkında konuşmadan çıkamayız. Bu mülk sarmalayıcı, uygulamanızda zaman damgalarıyla ilgili işlemler yapma konusunda çok güçlüdür.

Herhangi bir dağıtık sistemde, sistemlerdeki saatlerin her zaman tamamen senkronize olmaması olasıdır. Bunun önemli olmadığını düşünebilirsiniz ancak bir saatin hisse senedi alım satımı sistemiyle biraz senkronize olmamasının sonuçlarını düşünün: Bir milisaniyelik sapma bile bir işlemin yürütülmesinde milyonlarca dolarlık bir farka neden olabilir.

Cloud Firestore, @ServerTimestamp ile işaretlenmiş özellikleri aşağıdaki şekilde işler: Özelliği depolarken nil ise (örneğin, addDocument() kullanılarak) Cloud Firestore, alanı veritabanına yazılırken geçerli sunucu zaman damgasıyla doldurur. addDocument() veya updateData() çağrısını yaptığınızda alan nil değilse Cloud Firestore, özellik değerini değiştirmez. Böylece, createdAt ve lastUpdatedAt gibi alanları uygulamak kolaylaşır.

Coğrafi noktalar

Coğrafi konumlar uygulamalarımızda her zaman vardır. Birçok heyecan verici özellik bunları saklayarak mümkün hale gelir. Örneğin, uygulamanızın bir hedefe ulaştığınızda size bir görevi hatırlatabilmesi için görevle ilişkili bir konumu depolamak yararlı olabilir.

Cloud Firestore, her konumun enlem ve boylam bilgisini kaydedebilen yerleşik GeoPoint veri türüne sahiptir. Cloud Firestore belgesindeki konumları eşlemek için GeoPoint türünü kullanabiliriz:

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

Swift'te karşılık gelen tür CLLocationCoordinate2D'dür. Bu iki tür arasında aşağıdaki işlemi kullanarak eşleme yapabiliriz:

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

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

Sıralamalar

Listeler, Swift'teki en az değer verilen dil özelliklerinden biridir. Ancak göründüğünden çok daha fazlasını sunar. Listelerin yaygın kullanım alanlarından biri, bir şeyin ayrık durumlarını modellemedir. Örneğin, makaleleri yönetmek için bir uygulama yazabiliriz. Bir makalenin durumunu izlemek için bir enum Status kullanabiliriz:

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

Cloud Firestore, enum'leri doğal olarak desteklemez (yani değer kümesini zorunlu klamaz). Ancak yine de enum'lerin yazılabilir olmasından yararlanabilir ve kodlanabilir bir tür seçebiliriz. Bu örnekte String'yi seçtik. Bu işlem, Cloud Firestore dokümanında 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 sıralamayı ifade ettiğini bile özelleştirebiliriz. Örneğin, Status.inReview destek kaydını "inceleniyor" olarak kaydetmeye karar verirsek yukarıdaki enumu aşağıdaki gibi 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 özellik adları, Swift'teki veri modelimizdeki mülkleri adlarıyla eşleşmez. Örneğin, iş arkadaşlarımızdan biri Python geliştirici olabilir ve tüm özellik adları için snake_case seçeneğini tercih edebilir.

Endişelenmeyin: Codable bu konuda size yardımcı olacaktır.

Bu gibi durumlarda CodingKeys'ten yararlanabiliriz. Bu, belirli özelliklerin nasıl eşleneceğini belirtmek için kodlanabilir bir struct'a ekleyebileceğimiz bir sıralamadır.

Aşağıdaki dokümanı ele alalım:

snake_cased özellik adına sahip bir Firestore belgesi

Bu belgeyi String türünde ad özelliğine sahip bir struct ile eşlemek için ProgrammingLanguage struct'a bir CodingKeys sıralaması eklememiz ve belgede özelliğ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 özellik adlarını belirlemek için Swift türlerimizin özellik adlarını kullanır. Bu nedenle, özellik adları eşleştiğinde kodlanabilir türlerimize CodingKeys eklemenize gerek yoktur. Ancak belirli bir tür için CodingKeys'ü kullandıktan sonra eşlemek istediğimiz tüm mülk adlarını eklememiz gerekir.

Yukarıdaki kod snippet'inde, SwiftUI List görünümünde tanımlayıcı olarak kullanmak isteyebileceğimiz bir id mülkü tanımladık. CodingKeys içinde belirtilmezse veri getirilirken eşlenmez ve nil olur. Bu durumda List görünümü ilk dokümanla doldurulur.

İlgili CodingKeys enum'da büyük/küçük harf duyarlı olarak listelenmeyen tüm mülkler, eşleme işlemi sırasında yoksayılır. Özellikle bazı mülklerin haritalanması hariç tutulmak isteniyorsa bu özellik kullanışlı olabilir.

Örneğin, reasonWhyILoveThis özelliğini eşlemenin dışında bırakmak istersek tek yapmamız gereken bu özelliği CodingKeys sıralaması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 dokümanına tekrar boş bir özellik yazmak isteyebiliriz. Swift, değer olmadığını belirtmek için isteğe bağlı kavramına sahiptir ve Cloud Firestore, null değerlerini de destekler. Ancak nil değeri olan isteğe bağlı parametrelerin kodlanması için varsayılan davranış, bu parametreleri atlamaktır. @ExplicitNull, Swift isteğe bağlılarının kodlanırken nasıl ele alınacağı konusunda bize bir miktar kontrol sağlar: İsteğe bağlı bir mülkü @ExplicitNull olarak işaretleyerek Cloud Firestore'e bu mülkü, nil değeri içeriyorsa belgeye null değerle yazmasını söyleyebiliriz.

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

Codable ile veri eşleştirme kapsamımızın son konularından biri olarak, özel kodlayıcıları ve kod çözücüleri tanıtalım. Bu bölümde, doğal Cloud Firestore veri türü ele alınmamaktadır ancak özel kodlayıcılar ve kod çözücüler, Cloud Firestore uygulamalarınızda yaygın olarak kullanışlıdır.

"Renk eşlemesi nasıl yapabilirim?", geliştiriciler tarafından hem Cloud Firestore hem de Swift ile JSON arasında eşleme yapmak için en sık sorulan sorulardan biridir. Sunulan çok sayıda çözüm vardır, ancak çoğu JSON'a odaklanır ve bunların neredeyse tümü renkleri, RGB bileşenlerinden oluşan, iç içe yerleştirilmiş bir sözlük olarak eşler.

Daha iyi ve daha basit bir çözüm olmalı. Web renklerini (veya daha spesifik olarak CSS onaltılık renk gösterimini) neden kullanmıyoruz? Kullanımı kolaydır (temelde bir dizedir) ve hatta şeffaflığı destekler.

Bir Swift Color değerini onaltılık değerine eşleyebilmek için Color değerine Codable ekleyen bir Swift uzantısı oluşturmamız gerekir.

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

Bu sayede, etiket eşleme kodunu güncelleyebiliriz. Böylece, etiket renklerini uygulamamızın kullanıcı arayüzü kodunda manuel olarak eşlemek yerine doğrudan yönetmek daha kolay hale gelir:

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 snippet'lerinde hata işleme işlemini kasıtlı olarak minimum düzeyde tuttuk ancak üretim uygulamasında tüm hataları düzgün bir şekilde ele almanız gerekir.

Karşılaşabileceğiniz tüm hata durumlarını nasıl ele alacağınızı gösteren bir kod snippet'i aşağıda verilmiştir:

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ı ele alma

Önceki kod snippet'i, tek bir doküman getirilirken karşılaşılan hataların nasıl işleneceğini gösterir. Cloud Firestore, verileri bir kez getirmenin yanı sıra, anlık görüntü dinleyicileri adı verilen öğeleri kullanarak uygulamanıza güncellemeleri gerçekleştiği anda sunmayı da destekler. Bir koleksiyona (veya sorguya) anlık görüntü dinleyici kaydedebiliriz. Cloud Firestore, güncelleme olduğunda dinleyicimizi çağırır.

Aşağıda, anlık görüntü dinleyicisinin nasıl kaydedileceğini, Codable'ı kullanarak verilerin nasıl eşleneceğini ve oluşabilecek hataların nasıl ele alınacağını gösteren bir kod snippet'i verilmiştir. Ayrıca koleksiyona nasıl yeni doküman ekleneceği de gösterilmektedir. Göreceğiniz gibi, eşlenen dokümanları içeren yerel diziyi güncellememiz gerekmez. Bu işlem, anlık görüntü dinleyicisindeki kod tarafından yapılır.

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 yayında kullanılan tüm kod snippet'leri, bu GitHub deposundan indirebileceğiniz örnek bir uygulamanın parçasıdır.

Codable'ı kullanmaya başlayın.

Swift'in Codable API'si, serileştirilmiş biçimlerdeki verileri uygulamanızın veri modeliyle eşlemek için güçlü ve esnek bir yöntem sunar. Bu kılavuzda, veri deposu olarak Cloud Firestore kullanan uygulamalarda bu aracın ne kadar kolay kullanıldığını gördünüz.

Basit veri türlerine sahip temel bir örnekten başlayarak veri modelinin karmaşıklığını kademeli olarak artırdık. Bu süreçte, eşlemeyi bizim için gerçekleştirmek üzere Codable ve Firebase'in uygulamasına güvenebildik.

Codable hakkında daha fazla bilgi için aşağıdaki kaynakları inceleyebilirsiniz:

Cloud Firestore dokümanlarının haritasını çıkarmayla ilgili kapsamlı bir rehber derlemek için elimizden geleni yapmış olsak da bu, tam 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 veri türlerini eşlemek veya verileri Swift'te temsil etmek için hangi stratejileri kullandığınızı bize bildirin.Cloud Firestore

Cloud Firestore'ın Codable desteğini kullanmamak için hiçbir neden yok.