स्विफ्ट कोडेबल के साथ क्लाउड फायरस्टोर डेटा मैप करें

स्विफ्ट की कोडेबल एपीआई, स्विफ्ट 4 में पेश की गई, हमें कंपाइलर की शक्ति का लाभ उठाने में सक्षम बनाती है ताकि डेटा को क्रमबद्ध प्रारूपों से स्विफ्ट प्रकारों में मैप करना आसान हो सके।

हो सकता है कि आप वेब एपीआई से डेटा को अपने ऐप के डेटा मॉडल (और इसके विपरीत) में मैप करने के लिए कोडेबल का उपयोग कर रहे हों, लेकिन यह उससे कहीं अधिक लचीला है।

इस गाइड में, हम यह देखने जा रहे हैं कि क्लाउड फायरस्टोर से स्विफ्ट प्रकारों और इसके विपरीत डेटा को मैप करने के लिए कोडेबल का उपयोग कैसे किया जा सकता है।

क्लाउड फायरस्टोर से दस्तावेज़ लाते समय, आपके ऐप को कुंजी/मूल्य जोड़े का एक शब्दकोश प्राप्त होगा (या यदि आप एकाधिक दस्तावेज़ लौटाने वाले ऑपरेशनों में से किसी एक का उपयोग करते हैं तो शब्दकोशों की एक श्रृंखला)।

अब, आप निश्चित रूप से स्विफ्ट में सीधे शब्दकोशों का उपयोग करना जारी रख सकते हैं, और वे कुछ बेहतरीन लचीलापन प्रदान करते हैं जो कि आपके उपयोग के मामले में बिल्कुल वैसा ही हो सकता है। हालाँकि, यह दृष्टिकोण सुरक्षित नहीं है और विशेषता नामों की गलत वर्तनी द्वारा, या पिछले सप्ताह उस रोमांचक नई सुविधा को भेजते समय आपकी टीम द्वारा जोड़ी गई नई विशेषता को मैप करना भूल जाने से कठिन-से-ट्रैक-डाउन बग पेश करना आसान है।

अतीत में, कई डेवलपर्स ने एक सरल मैपिंग परत को लागू करके इन कमियों को दूर करने का काम किया है, जिससे उन्हें शब्दकोशों को स्विफ्ट प्रकारों में मैप करने की अनुमति मिली। लेकिन फिर, इनमें से अधिकांश कार्यान्वयन क्लाउड फायरस्टोर दस्तावेज़ों और आपके ऐप के डेटा मॉडल के संबंधित प्रकारों के बीच मैपिंग को मैन्युअल रूप से निर्दिष्ट करने पर आधारित हैं।

स्विफ्ट के कोडेबल एपीआई के लिए क्लाउड फायरस्टोर के समर्थन के साथ, यह बहुत आसान हो जाता है:

  • अब आपको किसी भी मैपिंग कोड को मैन्युअल रूप से लागू नहीं करना पड़ेगा।
  • यह परिभाषित करना आसान है कि विभिन्न नामों के साथ विशेषताओं को कैसे मैप किया जाए।
  • इसमें स्विफ्ट के कई प्रकारों के लिए अंतर्निहित समर्थन है।
  • और कस्टम प्रकारों की मैपिंग के लिए समर्थन जोड़ना आसान है।
  • सबसे अच्छी बात: सरल डेटा मॉडल के लिए, आपको कोई मैपिंग कोड बिल्कुल भी लिखने की ज़रूरत नहीं होगी।

डेटा मैपिंग

क्लाउड फायरस्टोर दस्तावेज़ों में डेटा संग्रहीत करता है जो कुंजियों को मानों से मैप करता है। किसी व्यक्तिगत दस्तावेज़ से डेटा लाने के लिए, हम DocumentSnapshot.data() कॉल कर सकते हैं, जो फ़ील्ड नामों को Any : func data() -> [String : Any]? .

इसका मतलब है कि हम प्रत्येक व्यक्तिगत फ़ील्ड तक पहुंचने के लिए स्विफ्ट के सबस्क्रिप्ट सिंटैक्स का उपयोग कर सकते हैं।

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 फ़ील्ड के लिए स्ट्रिंग चुन सकते हैं, जिसके परिणामस्वरूप खोजने में मुश्किल मैपिंग समस्या हो सकती है। साथ ही, जब भी कोई नया फ़ील्ड जोड़ा जाता है तो आपको अपना मैपिंग कोड अपडेट करना होगा, जो काफी बोझिल है।

और हमें यह नहीं भूलना चाहिए कि हम स्विफ्ट के मजबूत प्रकार के सिस्टम का लाभ नहीं उठा रहे हैं, जो Book के प्रत्येक गुण के लिए बिल्कुल सही प्रकार जानता है।

वैसे भी कोडेबल क्या है?

Apple के दस्तावेज़ीकरण के अनुसार, कोडेबल "एक प्रकार है जो स्वयं को बाहरी प्रतिनिधित्व में और उसके बाहर परिवर्तित कर सकता है।" वास्तव में, कोडेबल एनकोडेबल और डिकोडेबल प्रोटोकॉल के लिए एक प्रकार का उपनाम है। स्विफ्ट प्रकार को इस प्रोटोकॉल के अनुरूप बनाकर, कंपाइलर JSON जैसे क्रमबद्ध प्रारूप से इस प्रकार के उदाहरण को एनकोड/डीकोड करने के लिए आवश्यक कोड को संश्लेषित करेगा।

किसी पुस्तक के बारे में डेटा संग्रहीत करने का एक सरल प्रकार इस तरह दिख सकता है:

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

जैसा कि आप देख सकते हैं, प्रकार को कोडेबल के अनुरूप बनाना न्यूनतम आक्रामक है। हमें केवल प्रोटोकॉल में अनुरूपता जोड़नी थी; किसी अन्य परिवर्तन की आवश्यकता नहीं थी.

इसके साथ, अब हम किसी पुस्तक को 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)

क्लाउड फायरस्टोर दस्तावेज़ों में सरल प्रकारों से मैपिंग
कोडेबल का उपयोग करना

क्लाउड फायरस्टोर सरल स्ट्रिंग्स से लेकर नेस्टेड मानचित्रों तक, डेटा प्रकारों के एक विस्तृत सेट का समर्थन करता है। इनमें से अधिकांश सीधे स्विफ्ट के अंतर्निहित प्रकारों से मेल खाते हैं। आइए अधिक जटिल डेटा प्रकारों पर जाने से पहले कुछ सरल डेटा प्रकारों की मैपिंग पर एक नज़र डालें।

क्लाउड फायरस्टोर दस्तावेज़ों को स्विफ्ट प्रकारों में मैप करने के लिए, इन चरणों का पालन करें:

  1. सुनिश्चित करें कि आपने अपने प्रोजेक्ट में FirebaseFirestore फ्रेमवर्क जोड़ा है। ऐसा करने के लिए आप स्विफ्ट पैकेज मैनेजर या कोकोआपोड्स का उपयोग कर सकते हैं।
  2. अपनी स्विफ्ट फ़ाइल में FirebaseFirestore आयात करें।
  3. अपने प्रकार को Codable के अनुरूप बनाएँ।
  4. (वैकल्पिक, यदि आप List दृश्य में प्रकार का उपयोग करना चाहते हैं) अपने प्रकार में एक id प्रॉपर्टी जोड़ें, और क्लाउड फायरस्टोर को इसे दस्तावेज़ आईडी पर मैप करने के लिए कहने के लिए @DocumentID उपयोग करें। हम इस पर नीचे अधिक विस्तार से चर्चा करेंगे।
  5. दस्तावेज़ संदर्भ को स्विफ्ट प्रकार में मैप करने के लिए documentReference.data(as: ) का उपयोग करें।
  6. स्विफ्ट प्रकारों से क्लाउड फायरस्टोर दस्तावेज़ में डेटा मैप करने के लिए documentReference.setData(from: ) का उपयोग करें।
  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)
    }
  }
}

नया दस्तावेज़ जोड़ते समय, क्लाउड फायरस्टोर स्वचालित रूप से दस्तावेज़ में एक नई दस्तावेज़ आईडी निर्दिष्ट करने का ध्यान रखेगा। यह तब भी काम करता है जब ऐप वर्तमान में ऑफ़लाइन हो।

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

सरल डेटा प्रकारों को मैप करने के अलावा, क्लाउड फायरस्टोर कई अन्य डेटा प्रकारों का समर्थन करता है, जिनमें से कुछ संरचित प्रकार हैं जिनका उपयोग आप किसी दस्तावेज़ के अंदर नेस्टेड ऑब्जेक्ट बनाने के लिए कर सकते हैं।

नेस्टेड कस्टम प्रकार

अधिकांश विशेषताएँ जिन्हें हम अपने दस्तावेज़ों में मैप करना चाहते हैं वे सरल मान हैं, जैसे कि पुस्तक का शीर्षक या लेखक का नाम। लेकिन उन मामलों के बारे में क्या जब हमें अधिक जटिल वस्तु को संग्रहीत करने की आवश्यकता होती है? उदाहरण के लिए, हम पुस्तक के कवर के यूआरएल को अलग-अलग रिज़ॉल्यूशन में संग्रहीत करना चाह सकते हैं।

क्लाउड फायरस्टोर में ऐसा करने का सबसे आसान तरीका मानचित्र का उपयोग करना है:

फ़ायरस्टोर दस्तावेज़ में नेस्टेड कस्टम प्रकार संग्रहीत करना

संबंधित स्विफ्ट संरचना लिखते समय, हम इस तथ्य का उपयोग कर सकते हैं कि क्लाउड फायरस्टोर यूआरएल का समर्थन करता है - जब यूआरएल वाले फ़ील्ड को संग्रहीत करते समय, इसे एक स्ट्रिंग में परिवर्तित किया जाएगा और इसके विपरीत:

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 कैसे परिभाषित किया है। BookWithCoverImages पर कवर प्रॉपर्टी को वैकल्पिक के रूप में चिह्नित करके, हम इस तथ्य को संभालने में सक्षम हैं कि कुछ दस्तावेज़ों में कवर विशेषता नहीं हो सकती है।

यदि आप जानना चाहते हैं कि डेटा लाने या अपडेट करने के लिए कोई कोड स्निपेट क्यों नहीं है, तो आपको यह सुनकर खुशी होगी कि क्लाउड फायरस्टोर से पढ़ने या लिखने के लिए कोड को समायोजित करने की कोई आवश्यकता नहीं है: यह सब हमारे कोड के साथ काम करता है 'प्रारंभिक खंड में लिखा है.

सरणियों

कभी-कभी, हम किसी दस्तावेज़ में मानों का संग्रह संग्रहीत करना चाहते हैं। किसी पुस्तक की शैलियाँ एक अच्छा उदाहरण हैं: द हिचहाइकर गाइड टू द गैलेक्सी जैसी पुस्तक कई श्रेणियों में आ सकती है - इस मामले में "विज्ञान-फाई" और "कॉमेडी":

फ़ायरस्टोर दस्तावेज़ में एक सरणी संग्रहीत करना

क्लाउड फायरस्टोर में, हम मानों की एक श्रृंखला का उपयोग करके इसे मॉडल कर सकते हैं। यह किसी भी कोडेबल प्रकार (जैसे String , Int , आदि) के लिए समर्थित है। निम्नलिखित दिखाता है कि हमारे Book मॉडल में शैलियों की एक श्रृंखला कैसे जोड़ें:

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

चूँकि यह किसी भी कोडेबल प्रकार के लिए काम करता है, हम कस्टम प्रकार का भी उपयोग कर सकते हैं। कल्पना कीजिए कि हम प्रत्येक पुस्तक के लिए टैग की एक सूची संग्रहीत करना चाहते हैं। टैग के नाम के साथ, हम टैग का रंग भी इस तरह संग्रहित करना चाहेंगे:

फायरस्टोर दस्तावेज़ में कस्टम प्रकारों की एक श्रृंखला संग्रहीत करना

टैग को इस तरह से संग्रहीत करने के लिए, हमें बस एक टैग का प्रतिनिधित्व करने और उसे कोड करने योग्य बनाने के लिए एक Tag संरचना लागू करने की आवश्यकता है:

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

और ठीक इसी तरह, हम अपने Book दस्तावेज़ों में Tags की एक श्रृंखला संग्रहीत कर सकते हैं!

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

दस्तावेज़ आईडी मैप करने के बारे में एक त्वरित शब्द

इससे पहले कि हम और अधिक प्रकारों की मैपिंग की ओर बढ़ें, आइए एक पल के लिए दस्तावेज़ आईडी की मैपिंग के बारे में बात करें।

हमने अपने क्लाउड फायरस्टोर दस्तावेज़ों की दस्तावेज़ आईडी को हमारे स्विफ्ट प्रकारों की id प्रॉपर्टी में मैप करने के लिए पिछले कुछ उदाहरणों में @DocumentID प्रॉपर्टी रैपर का उपयोग किया था। यह कई कारणों से महत्वपूर्ण है:

  • इससे हमें यह जानने में मदद मिलती है कि उपयोगकर्ता द्वारा स्थानीय परिवर्तन किए जाने की स्थिति में किस दस्तावेज़ को अपडेट किया जाए।
  • स्विफ्टयूआई की List तत्वों को Identifiable होना आवश्यक है ताकि तत्वों को सम्मिलित होने पर इधर-उधर जाने से रोका जा सके।

यह इंगित करने योग्य है कि दस्तावेज़ को वापस लिखते समय @DocumentID के रूप में चिह्नित विशेषता को क्लाउड फायरस्टोर के एनकोडर द्वारा एन्कोड नहीं किया जाएगा। ऐसा इसलिए है क्योंकि दस्तावेज़ आईडी स्वयं दस्तावेज़ की विशेषता नहीं है - इसलिए इसे दस्तावेज़ में लिखना एक गलती होगी।

नेस्टेड प्रकारों के साथ काम करते समय (जैसे कि इस गाइड में पिछले उदाहरण में Book पर टैग की सरणी), @DocumentID प्रॉपर्टी जोड़ने की आवश्यकता नहीं है: नेस्टेड प्रॉपर्टीज़ क्लाउड फायरस्टोर दस्तावेज़ का एक हिस्सा हैं, और इसका गठन नहीं होता है एक अलग दस्तावेज़. इसलिए, उन्हें दस्तावेज़ आईडी की आवश्यकता नहीं है।

दिनांक और समय

क्लाउड फायरस्टोर में दिनांक और समय को संभालने के लिए एक अंतर्निहित डेटा प्रकार है, और कोडेबल के लिए क्लाउड फायरस्टोर के समर्थन के लिए धन्यवाद, उनका उपयोग करना आसान है।

आइए इस दस्तावेज़ पर एक नज़र डालें जो 1843 में आविष्कार की गई सभी प्रोग्रामिंग भाषाओं की जननी, एडा का प्रतिनिधित्व करता है:

फ़ायरस्टोर दस्तावेज़ में दिनांक संग्रहीत करना

इस दस्तावेज़ को मैप करने के लिए स्विफ्ट प्रकार इस तरह दिख सकता है:

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

हम दिनांक और समय के बारे में इस अनुभाग को @ServerTimestamp के बारे में बातचीत किए बिना नहीं छोड़ सकते। जब आपके ऐप में टाइमस्टैम्प से निपटने की बात आती है तो यह प्रॉपर्टी रैपर एक पावरहाउस है।

किसी भी वितरित सिस्टम में, संभावना यह है कि अलग-अलग सिस्टम की घड़ियाँ हर समय पूरी तरह से सिंक में नहीं होती हैं। आप सोच सकते हैं कि यह कोई बड़ी बात नहीं है, लेकिन एक शेयर व्यापार प्रणाली के लिए घड़ी के थोड़े से तालमेल से बाहर होने के निहितार्थ की कल्पना करें: यहां तक ​​कि एक मिलीसेकंड विचलन के परिणामस्वरूप व्यापार निष्पादित करते समय लाखों डॉलर का अंतर हो सकता है।

क्लाउड फायरस्टोर @ServerTimestamp के साथ चिह्नित विशेषताओं को निम्नानुसार संभालता है: यदि विशेषता संग्रहीत करते समय nil है (उदाहरण के लिए addDocument() का उपयोग करके), तो क्लाउड फायरस्टोर डेटाबेस में लिखते समय वर्तमान सर्वर टाइमस्टैम्प के साथ फ़ील्ड को पॉप्युलेट करेगा . यदि addDocument() या updateData() को कॉल करने पर फ़ील्ड nil नहीं है, तो क्लाउड फायरस्टोर विशेषता मान को अछूता छोड़ देगा। इस तरह, createdAt और lastUpdatedAt जैसे फ़ील्ड को लागू करना आसान है।

भूबिंदु

हमारे ऐप्स में जियोलोकेशन सर्वव्यापी हैं। इन्हें संग्रहीत करने से कई रोमांचक सुविधाएँ संभव हो जाती हैं। उदाहरण के लिए, किसी कार्य के लिए स्थान संग्रहीत करना उपयोगी हो सकता है ताकि जब आप किसी गंतव्य पर पहुंचें तो आपका ऐप आपको कार्य के बारे में याद दिला सके।

क्लाउड फायरस्टोर में एक अंतर्निहित डेटा प्रकार, GeoPoint है, जो किसी भी स्थान के देशांतर और अक्षांश को संग्रहीत कर सकता है। क्लाउड फायरस्टोर दस्तावेज़ से स्थानों को मैप करने के लिए, हम GeoPoint प्रकार का उपयोग कर सकते हैं:

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

स्विफ्ट में संबंधित प्रकार CLLocationCoordinate2D है, और हम निम्नलिखित ऑपरेशन के साथ उन दो प्रकारों के बीच मैप कर सकते हैं:

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

भौतिक स्थान के आधार पर दस्तावेज़ों की क्वेरी करने के बारे में अधिक जानने के लिए, इस समाधान मार्गदर्शिका को देखें।

एनम्स

स्विफ्ट में एनम संभवतः सबसे कम मूल्यांकित भाषा सुविधाओं में से एक है; उनमें जो दिखता है उससे कहीं अधिक है। एनम के लिए एक सामान्य उपयोग का मामला किसी चीज़ की अलग-अलग स्थितियों का मॉडल बनाना है। उदाहरण के लिए, हम लेखों को प्रबंधित करने के लिए एक ऐप लिख रहे होंगे। किसी लेख की स्थिति को ट्रैक करने के लिए, हम एक एनम Status उपयोग करना चाह सकते हैं:

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

क्लाउड फायरस्टोर मूल रूप से एनम का समर्थन नहीं करता है (यानी, यह मानों के सेट को लागू नहीं कर सकता है), लेकिन हम अभी भी इस तथ्य का उपयोग कर सकते हैं कि एनम टाइप किया जा सकता है, और एक कोडेबल प्रकार चुन सकते हैं। इस उदाहरण में, हमने String चुना है, जिसका अर्थ है कि क्लाउड फायरस्टोर दस्तावेज़ में संग्रहीत होने पर सभी एनम मान स्ट्रिंग से/में मैप किए जाएंगे।

और, चूंकि स्विफ्ट कस्टम कच्चे मूल्यों का समर्थन करता है, हम यह भी अनुकूलित कर सकते हैं कि कौन से मान किस एनम केस को संदर्भित करते हैं। उदाहरण के लिए, यदि हमने Status.inReview मामले को "समीक्षा में" के रूप में संग्रहीत करने का निर्णय लिया है, तो हम उपरोक्त एनम को निम्नानुसार अपडेट कर सकते हैं:

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

मैपिंग को अनुकूलित करना

कभी-कभी, क्लाउड फायरस्टोर दस्तावेज़ों के विशेषता नाम जिन्हें हम मैप करना चाहते हैं, स्विफ्ट में हमारे डेटा मॉडल में गुणों के नामों से मेल नहीं खाते हैं। उदाहरण के लिए, हमारा एक सहकर्मी पायथन डेवलपर हो सकता है, और उसने अपने सभी विशेषता नामों के लिए स्नेक_केस चुनने का निर्णय लिया है।

चिंता न करें: कोडेबल ने हमें कवर कर लिया है!

ऐसे मामलों के लिए, हम CodingKeys का उपयोग कर सकते हैं। यह एक एनम है जिसे हम यह निर्दिष्ट करने के लिए एक कोडेबल संरचना में जोड़ सकते हैं कि कुछ विशेषताओं को कैसे मैप किया जाएगा।

इस दस्तावेज़ पर विचार करें:

स्नेक_केस्ड विशेषता नाम वाला एक फायरस्टोर दस्तावेज़

इस दस्तावेज़ को उस संरचना में मैप करने के लिए जिसमें String प्रकार की नाम संपत्ति है, हमें ProgrammingLanguage संरचना में एक CodingKeys एनम जोड़ना होगा, और दस्तावेज़ में विशेषता का नाम निर्दिष्ट करना होगा:

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

डिफ़ॉल्ट रूप से, कोडेबल एपीआई हमारे स्विफ्ट प्रकारों के संपत्ति नामों का उपयोग क्लाउड फायरस्टोर दस्तावेज़ों पर विशेषता नाम निर्धारित करने के लिए करेगा जिन्हें हम मैप करने का प्रयास कर रहे हैं। इसलिए जब तक विशेषता नाम मेल खाते हैं, हमारे कोडेबल प्रकारों में CodingKeys जोड़ने की कोई आवश्यकता नहीं है। हालाँकि, एक बार जब हम किसी विशिष्ट प्रकार के लिए CodingKeys उपयोग करते हैं, तो हमें उन सभी संपत्ति नामों को जोड़ना होगा जिन्हें हम मैप करना चाहते हैं।

उपरोक्त कोड स्निपेट में, हमने एक id प्रॉपर्टी परिभाषित की है जिसे हम स्विफ्टयूआई List दृश्य में पहचानकर्ता के रूप में उपयोग करना चाहेंगे। यदि हमने इसे 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
  }
}

कभी-कभी हम क्लाउड फायरस्टोर दस्तावेज़ में एक खाली विशेषता लिखना चाह सकते हैं। स्विफ्ट में किसी मूल्य की अनुपस्थिति को दर्शाने के लिए वैकल्पिक की धारणा है, और क्लाउड फायरस्टोर null मानों का भी समर्थन करता है। हालाँकि, nil मान वाले एन्कोडिंग विकल्पों के लिए डिफ़ॉल्ट व्यवहार बस उन्हें छोड़ देना है। @ExplicitNull हमें इस पर कुछ नियंत्रण देता है कि एन्कोडिंग करते समय स्विफ्ट वैकल्पिक को कैसे संभाला जाता है: एक वैकल्पिक संपत्ति को @ExplicitNull के रूप में चिह्नित करके, हम क्लाउड फायरस्टोर को इस संपत्ति को शून्य मान के साथ दस्तावेज़ में लिखने के लिए कह सकते हैं यदि इसमें nil का मान है।

रंगों की मैपिंग के लिए एक कस्टम एनकोडर और डिकोडर का उपयोग करना

कोडेबल के साथ डेटा मैपिंग के हमारे कवरेज में अंतिम विषय के रूप में, आइए कस्टम एनकोडर और डिकोडर का परिचय दें। यह अनुभाग मूल क्लाउड फायरस्टोर डेटाटाइप को कवर नहीं करता है, लेकिन कस्टम एनकोडर और डिकोडर आपके क्लाउड फायरस्टोर ऐप्स में व्यापक रूप से उपयोगी हैं।

"मैं रंगों को कैसे मैप कर सकता हूं" सबसे अधिक बार पूछे जाने वाले डेवलपर प्रश्नों में से एक है, न केवल क्लाउड फायरस्टोर के लिए, बल्कि स्विफ्ट और JSON के बीच मैपिंग के लिए भी। वहाँ बहुत सारे समाधान हैं, लेकिन उनमें से अधिकांश JSON पर ध्यान केंद्रित करते हैं, और उनमें से लगभग सभी अपने RGB घटकों से बने नेस्टेड शब्दकोश के रूप में रंगों को मैप करते हैं।

ऐसा लगता है कि कोई बेहतर, सरल समाधान होना चाहिए। हम वेब रंगों का उपयोग क्यों नहीं करते (या, अधिक विशिष्ट होने के लिए, सीएसएस हेक्स रंग नोटेशन) - उनका उपयोग करना आसान है (अनिवार्य रूप से सिर्फ एक स्ट्रिंग), और वे पारदर्शिता का भी समर्थन करते हैं!

स्विफ्ट Color उसके हेक्स मान पर मैप करने में सक्षम होने के लिए, हमें एक स्विफ्ट एक्सटेंशन बनाने की आवश्यकता है जो 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() का उपयोग करके, हम RGBA घटकों को नेस्ट किए बिना, एक String को उसके Color समकक्ष में डिकोड कर सकते हैं। साथ ही, आप इन मानों को अपने ऐप के वेब यूआई में पहले परिवर्तित किए बिना उपयोग कर सकते हैं!

इसके साथ, हम मैपिंग टैग के लिए कोड अपडेट कर सकते हैं, जिससे टैग रंगों को हमारे ऐप के यूआई कोड में मैन्युअल रूप से मैप करने के बजाय सीधे संभालना आसान हो जाता है:

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

लाइव अपडेट में त्रुटियों को संभालना

पिछला कोड स्निपेट दर्शाता है कि एकल दस्तावेज़ लाते समय त्रुटियों को कैसे संभालना है। एक बार डेटा प्राप्त करने के अलावा, क्लाउड फायरस्टोर तथाकथित स्नैपशॉट श्रोताओं का उपयोग करके आपके ऐप पर अपडेट देने का भी समर्थन करता है: हम एक स्नैपशॉट श्रोता को संग्रह (या क्वेरी) पर पंजीकृत कर सकते हैं, और क्लाउड फायरस्टोर हमारे श्रोता को जब भी वहां कॉल करेगा एक अद्यतन है.

यहां एक कोड स्निपेट है जो दिखाता है कि स्नैपशॉट श्रोता को कैसे पंजीकृत किया जाए, कोडेबल का उपयोग करके डेटा को कैसे मैप किया जाए और होने वाली किसी भी त्रुटि को कैसे संभाला जाए। यह यह भी दिखाता है कि संग्रह में नया दस्तावेज़ कैसे जोड़ा जाए। जैसा कि आप देखेंगे, मैप किए गए दस्तावेज़ों को रखने वाले स्थानीय सरणी को स्वयं अपडेट करने की कोई आवश्यकता नहीं है, क्योंकि स्नैपशॉट श्रोता में कोड द्वारा इसका ध्यान रखा जाता है।

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 रिपॉजिटरी से डाउनलोड कर सकते हैं।

आगे बढ़ें और कोडेबल का उपयोग करें!

स्विफ्ट का कोडेबल एपीआई क्रमबद्ध प्रारूपों से आपके एप्लिकेशन डेटा मॉडल तक डेटा को मैप करने का एक शक्तिशाली और लचीला तरीका प्रदान करता है। इस गाइड में, आपने देखा कि क्लाउड फायरस्टोर को अपने डेटास्टोर के रूप में उपयोग करने वाले ऐप्स में इसका उपयोग करना कितना आसान है।

सरल डेटा प्रकारों के साथ एक बुनियादी उदाहरण से शुरू करके, हमने डेटा मॉडल की जटिलता को उत्तरोत्तर बढ़ाया, साथ ही हमारे लिए मैपिंग करने के लिए कोडेबल और फायरबेस के कार्यान्वयन पर भरोसा करने में सक्षम हुए।

कोडेबल के बारे में अधिक जानकारी के लिए, मैं निम्नलिखित संसाधनों की अनुशंसा करता हूं:

हालाँकि हमने क्लाउड फायरस्टोर दस्तावेज़ों को मैप करने के लिए एक व्यापक मार्गदर्शिका संकलित करने की पूरी कोशिश की, लेकिन यह संपूर्ण नहीं है, और हो सकता है कि आप अपने प्रकारों को मैप करने के लिए अन्य रणनीतियों का उपयोग कर रहे हों। नीचे फीडबैक भेजें बटन का उपयोग करके, हमें बताएं कि आप अन्य प्रकार के क्लाउड फायरस्टोर डेटा को मैप करने या स्विफ्ट में डेटा का प्रतिनिधित्व करने के लिए किन रणनीतियों का उपयोग करते हैं।

क्लाउड फायरस्टोर के कोडेबल समर्थन का उपयोग न करने का वास्तव में कोई कारण नहीं है।