Cloud Firestore verilerini Swift Codable ile eşleme

Swift 4'te kullanıma sunulan Swift Codable API, derleyicinin gücünden yararlanarak seri hale getirilmiş biçimlerdeki verilerin Swift türlerine eşlenmesini kolaylaştırır.

Bir web API'sindeki verileri uygulamanızın veri modeline (veya tam tersi) eşlemek için Codable'ı kullanmış olabilirsiniz ancak Codable, bu işlevden çok daha esnektir.

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

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

Artık Swift'te sözlükleri doğrudan kullanmaya devam edebilirsiniz. Bu sözlükler, kullanım alanınızın tam olarak ihtiyaç duyduğu esnekliği 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 sunduğunda eklediği yeni özelliği eşlemeyi unutarak izi zor bulunan hatalar eklemek 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 gidermeye çalıştı. Ancak yine de bu uygulamaların çoğu, Cloud Firestore belgeler ile uygulamanızın veri modelinin karşılık gelen türleri arasındaki eşlemenin manuel olarak belirtilmesine dayanır.

Cloud Firestore'ın Swift'in Codable API'si için verdiği destek sayesinde bu işlem çok daha kolay hale geliyor:

  • Artık herhangi bir eşleme kodunu manuel olarak uygulamanız gerekmeyecek.
  • Farklı adlara sahip özelliklerin nasıl eşleneceğini kolayca tanımlayabilirsiniz.
  • Swift'in birçok türü için yerleşik destek sunar.
  • Ayrıca, özel türleri eşleme desteği eklemek de kolaydır.
  • En iyi yanı ise basit veri modelleri için herhangi bir eşleme kodu yazmanız gerekmez.

Eşleme verileri

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

Bu, her bir alana erişmek için Swift'in alt simge söz dizimini 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)
      }
    }
  }
}

Bu kod basit ve uygulaması kolay gibi görünse de hassastır, bakımı zordur ve hatalara açıktır.

Gördüğünüz gibi, doküman alanlarının veri türleriyle ilgili varsayımlarda bulunuyoruz. Bunlar doğru olabilir veya olmayabilir.

Ş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 durumda, bulunması zor bir eşleme sorunu ortaya çıkar. Ayrıca, yeni bir alan eklendiğinde eşleme kodunuzu güncellemeniz gerekir. Bu da oldukça zahmetlidir.

Ayrıca, Book özelliklerinin her biri için doğru türü tam olarak bilen Swift'in güçlü tür sisteminden yararlanmadığımızı da unutmayalım.

Codable nedir?

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

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ü Codable'a uygun hale getirmek çok az değişiklik gerektirir. Yalnızca protokole uygunluk eklememiz gerekti. Başka değişiklik yapılması gerekmedi.

Bu işlem tamamlandıktan sonra artık bir kitabı kolayca 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 kodunun çözülmesi şu şekilde ç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 belgelerindeki
basit türlerle eşleme yapma

Cloud Firestore, basit dizelerden iç içe yerleştirilmiş haritalara kadar geniş bir veri türü grubunu destekler. Bunların çoğu doğrudan Swift'in yerleşik türleriyle eşleşir. Daha karmaşık veri türlerine geçmeden önce bazı basit veri türlerinin nasıl eşlendiğine 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 öğesini Swift dosyanıza aktarın.
  3. Türünüzü Codable ile uyumlu hale getirin.
  4. (İsteğe bağlı, türü List görünümünde kullanmak istiyorsanız) Türünüze bir id özellik 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: ) kullanın.
  6. Swift türlerindeki verileri bir documentReference.setData(from: ) dokümanına eşlemek için Cloud Firestore kullanın.
  7. (İsteğe bağlıdır ancak kesinlikle önerilir) Uygun 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 @DocumentID özellik sarmalayıcısıyla ek açıklamalar eklememiz gerekiyordu.

Bir dokümanı getirmek ve eşlemek için kullanılan ö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 belge türünü belirterek bunu daha da kısa bir şekilde yazabilirsiniz. Bu işlem, eşlemeyi sizin için gerçekleştirir ve eşlenen belgeyi içeren bir Result türü döndürür. Kod çözme işlemi başarısız olursa hata döndürülü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. Temel hata işleme de dahil olmak üzere, Book örneğini kaydetmek için gereken kodu aşağıda bulabilirsiniz:

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 gerçekleştirir. Bu özellik, uygulama ç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 dizi başka veri türünü de destekler. Bunlardan bazıları, bir belgenin 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 gereken durumlarda ne yapmalıyız? Örneğin, kitabın kapağının URL'lerini farklı çözünürlüklerde saklamak isteyebiliriz.

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

İç içe yerleştirilmiş özel türü Firestore belgesinde depolama

İlgili Swift yapısını yazarken Cloud Firestore'nın URL'leri desteklediği gerçeğinden yararlanabiliriz. URL içeren bir alan depolanırken dizeye, dize depolanırken de URL'ye dönüştürülür:

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 CoverImages yapısını nasıl tanımladığımıza dikkat edin. BookWithCoverImages üzerindeki kapak özelliğini isteğe bağlı olarak işaretleyerek bazı belgelerde kapak özelliği bulunmayabileceği gerçeğini ele alabiliyoruz.

Veri getirme veya güncelleme için neden kod snippet'i olmadığını merak ediyorsanız Cloud Firestore'dan okuma ya da Cloud Firestore'ya yazma için kodu ayarlamanıza gerek olmadığını öğrenmek sizi memnun edecektir. Tüm bunlar, ilk bölümde yazdığımız kodla çalışır.

Diziler

Bazen bir dokümanda değer koleksiyonu saklamak isteriz. Kitap türleri iyi bir örnektir: Otostopçunun Galaksi Rehberi gibi bir kitap birkaç kategoriye (bu örnekte "Bilim Kurgu" ve "Komedi") girebilir:

Firestore belgesinde dizi depolama

Cloud Firestore içinde bunu bir değer dizisi kullanarak modelleyebiliriz. Bu, kodlanabilir tüm türler (ör. String, Int vb.) için desteklenir. Aşağıdaki örnekte, Book modelimize nasıl tür dizisi 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 yöntem, kodlanabilir tüm türlerde çalıştığı için özel türleri de kullanabiliriz. Her kitap için bir etiket listesi saklamak istediğimizi varsayalım. Etiketin adının yanı sıra rengini de aşağıdaki gibi saklamak istiyoruz:

Firestore belgesinde özel tür dizisi depolama

Etiketleri bu şekilde depolamak için yapmamız gereken tek şey, bir etiketi temsil etmek üzere Tag yapısını uygulamak ve bunu kodlanabilir hale getirmektir:

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

Bu şekilde, Tags dizisini Book dokümanlarımızda 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 bilgi

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

Önceki örneklerin bazılarında, @DocumentID dokümanlarımızın doküman kimliğini Swift türlerimizin id özelliğine eşlemek için @DocumentID özellik sarmalayıcısını kullandık.Cloud Firestore Bu durumun birkaç önemli nedeni vardır:

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

@DocumentID olarak işaretlenen bir özelliğin, doküman geri yazılırken Cloud Firestore kodlayıcısı tarafından kodlanmayacağını belirtmekte fayda var. Bunun nedeni, doküman kimliğinin dokümanın kendisiyle ilgili bir özellik olmamasıdır. Bu nedenle, dokümana yazılması hata olur.

İç içe yerleştirilmiş türlerle (ör. bu kılavuzdaki önceki bir örnekte Book içindeki etiket dizisi) çalışırken @DocumentID özelliği eklemeniz gerekmez: İç içe yerleştirilmiş özellikler, Cloud Firestore dokümanının bir parçasıdır ve ayrı bir doküman oluşturmaz. Bu nedenle, doküman 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'ın Codable desteği sayesinde bu türleri kullanmak kolaydır.

Tüm programlama dillerinin anası olarak kabul edilen ve 1843'te icat edilen Ada'yı temsil eden bu belgeye göz atalım:

Tarihleri Firestore belgesinde depolama

Bu belgeyi eşlemek için kullanılan bir Swift türü şu şekilde 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ümü bitiremeyiz. Bu özellik sarmalayıcısı, uygulamanızdaki zaman damgalarıyla ilgilenme konusunda oldukça kullanışlıdır.

Dağıtılmış sistemlerde, sistemlerin saatleri her zaman tamamen senkronize olmayabilir. Bunun büyük bir sorun olmadığını düşünebilirsiniz ancak biraz senkronize olmayan bir saatin borsa ticaret sistemi üzerindeki etkilerini düşünün: Bir ticareti gerçekleştirirken milisaniyelik bir sapma bile milyonlarca dolarlık bir farka neden olabilir.

Cloud Firestore, @ServerTimestamp ile işaretlenmiş özellikleri aşağıdaki şekilde işler: Özellik, siz depoladığınızda nil ise (örneğin, addDocument() kullanılarak) Cloud Firestore, alanı veritabanına yazıldığı sırada geçerli sunucu zaman damgasıyla doldurur. Alan, nil addDocument() veya updateData()'ı çağırdığınızda addDocument() değilse Cloud Firestore, özellik değerine dokunmaz. Bu sayede, createdAt ve lastUpdatedAt gibi alanları kolayca uygulayabilirsiniz.

Geopoints

Uygulamalarımızda coğrafi konumlar her yerde bulunur. Bu veriler depolanarak birçok heyecan verici özellik kullanılabilir. Örneğin, uygulamanız bir hedefe ulaştığınızda görevi size hatırlatabilmesi için bir görevin konumunu depolamak yararlı olabilir.

Cloud Firestore, herhangi bir konumun boylam ve enlemini depolayabilen yerleşik bir veri türü olan GeoPoint'ye sahiptir. Bir Cloud Firestore dokümanındaki konumları eşlemek için GeoPoint türünü kullanabiliriz:

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

Swift'teki karşılık gelen tür CLLocationCoordinate2D'dır ve aşağıdaki işlemle bu iki tür arasında 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.

Sıralamalar

Numaralandırmalar, Swift'teki en az değer verilen dil özelliklerinden biridir. Numaralandırılmış türlerin yaygın kullanım alanlarından biri, bir şeyin ayrı durumlarını modellemektir. Örneğin, makaleleri yönetmek için bir uygulama yazıyor olabiliriz. Bir makalenin durumunu izlemek için enum Status kullanabiliriz:

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

Cloud Firestore, numaralandırmaları yerel olarak desteklemez (yani değer kümesini zorlayamaz) ancak numaralandırmaların türü belirlenebilir ve kodlanabilir bir tür seçilebilir. Bu örnekte String seçilmiştir. Bu, Cloud Firestore dokümanında depolanırken tüm enum değerlerinin dizeye/dizeden eşleneceği anlamına gelir.

Ayrıca Swift, özel ham değerleri desteklediğinden hangi değerlerin hangi enum durumuna karşılık geldiğini bile özelleştirebiliriz. Örneğin, Status.inReview durumunu "inceleniyor" olarak saklamaya karar verirsek yukarıdaki enum'ı ş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 özellik adları, Swift'teki veri modelimizdeki özelliklerin adlarıyla eşleşmez. Örneğin, iş arkadaşlarımızdan biri Python geliştiricisi olabilir ve tüm özellik adları için snake_case kullanmaya karar vermiş olabilir.

Endişelenmeyin: Codable bu konuda bize yardımcı oluyor.

Bu gibi durumlarda CodingKeys özelliğinden yararlanabiliriz. Bu, belirli özelliklerin nasıl eşleneceğini belirtmek için kodlanabilir bir yapıya ekleyebileceğimiz bir enum'dur.

Şu belgeyi ele alalım:

Snake_case biçiminde özellik adına sahip bir Firestore belgesi

Bu belgeyi, String türünde bir ad özelliğine sahip bir yapıya eşlemek için ProgrammingLanguage yapısına bir CodingKeys enum eklememiz ve belgedeki ö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
  }
}

Codable API, varsayılan olarak 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ği sürece kodlanabilir türlerimize CodingKeys eklemenize gerek yoktur. Ancak belirli bir tür için CodingKeys kullandığımızda eşlemek istediğimiz tüm özellik 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 özelliği tanımladık. CodingKeys içinde belirtmediğimiz için veriler getirilirken eşlenmez ve bu nedenle nil olur. Bu durumda List görünümü ilk dokümanla doldurulur.

İlgili CodingKeys enum'unda vaka olarak listelenmeyen tüm özellikler eşleme işlemi sırasında yoksayılır. Bu, bazı mülklerin özellikle eşlenmesini istemiyorsak kullanışlı olabilir.

Örneğin, reasonWhyILoveThis mülkünün eşlenmesini istemiyorsak yapmamız gereken tek şey bu mülkü CodingKeys enum'undan 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 boş bir özelliği Cloud Firestore dokümanına geri yazmak isteyebiliriz. Swift'te değerin olmadığını belirtmek için isteğe bağlı değerler kavramı vardır ve Cloud Firestore, null değerlerini de destekler. Ancak nil değeri olan isteğe bağlı alanların kodlanmasıyla ilgili varsayılan davranış, bu alanları atlamaktır. @ExplicitNull, Swift isteğe bağlı değerlerinin kodlanırken nasıl işleneceği konusunda bize biraz kontrol sağlar: İsteğe bağlı bir özelliği @ExplicitNull olarak işaretleyerek Cloud Firestore'a, nil değeri içeriyorsa bu özelliği belgeye boş değerle yazmasını söyleyebiliriz.

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

Codable ile eşleme verileri kapsamındaki son konumuz olarak özel kodlayıcıları ve kod çözücüleri tanıtacağız. Bu bölümde yerel bir Cloud Firestore veri türü ele alınmamaktadır ancak özel kodlayıcılar ve kod çözücüler Cloud Firestore uygulamalarınızda oldukça faydalıdır.

"Renkleri nasıl eşleyebilirim?" sorusu, geliştiriciler tarafından en sık sorulan sorulardan biridir. Bu soru yalnızca Cloud Firestore için değil, Swift ile JSON arasındaki eşleme için de geçerlidir. Piyasada birçok çözüm olsa da çoğu JSON'a odaklanır ve neredeyse tamamı 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 olması gerekir. Neden web renklerini (veya daha spesifik olarak CSS onaltılık renk gösterimini) kullanmıyoruz? Bunları kullanmak kolaydır (temelde yalnızca bir dizedir) ve hatta şeffaflığı destekler.

Bir Swift Color öğesini onaltılık değerine eşleyebilmek için Color öğesine 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 String değerini 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, etiketleri eşlemek için kodu güncelleyebiliriz. Böylece, etiket renklerini doğrudan uygulamamızın kullanıcı arayüzü kodunda manuel olarak eşlemek yerine daha kolay bir şekilde yönetebiliriz:

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şlemeyi kasıtlı olarak minimum düzeyde tuttuk ancak bir üretim uygulamasında hataları düzgün bir şekilde işlemeyi unutmayın.

Karşılaşabileceğiniz hata durumlarının nasıl ele alınacağını 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'inde, tek bir doküman getirilirken hataların nasıl işleneceği gösterilmektedir. Cloud Firestore, verileri yalnızca bir kez getirmenin yanı sıra anlık görüntü dinleyicileri adı verilen bir yöntemle güncellemeleri gerçekleştiği anda uygulamanıza göndermeyi de destekler. Bir koleksiyona (veya sorguya) anlık görüntü dinleyicisi kaydedebiliriz ve bir güncelleme olduğunda Cloud Firestore dinleyicimizi çağırır.

Aşağıda, 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 işleneceğini gösteren bir kod snippet'i verilmiştir. Ayrıca, koleksiyona yeni bir belgenin nasıl ekleneceği de gösterilir. Gördüğünüz gibi, eşlenen dokümanları tutan yerel diziyi kendimiz güncellememize gerek yoktur. Bu işlem, anlık görüntü dinleyicisindeki kod tarafından gerçekleştirilir.

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 bir örnek uygulamanın parçasıdır.

Hemen Codable'ı kullanmaya başlayın.

Swift'in Codable API'si, verileri serileştirilmiş biçimlerden uygulama veri modelinize ve uygulama veri modelinizden serileştirilmiş biçimlere eşlemek için güçlü ve esnek bir yöntem sunar. Bu kılavuzda, Cloud Firestore'ı veri deposu olarak kullanan uygulamalarda kullanmanın ne kadar kolay olduğunu gördünüz.

Basit veri türleriyle temel bir örnekten başlayarak veri modelinin karmaşıklığını kademeli olarak artırdık. Bu sırada, eşlemeyi bizim için gerçekleştirmek üzere Codable ve Firebase'in uygulamasından yararlanabildik.

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

Cloud Firestore belgelerini eşleme konusunda kapsamlı bir rehber hazırlamak için elimizden geleni yapsak da bu rehber eksiksiz 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 türlerindeki verileri eşlemek veya verileri Swift'te temsil etmek için kullandığınız stratejileri bize bildirin.

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