Swift 4 में पेश किया गया, Swift का Codable API, कंपाइलर की मदद से डेटा को सीरियलाइज़ किए गए फ़ॉर्मैट से Swift टाइप में मैप करने की सुविधा देता है.
हो सकता है कि आपने Codable का इस्तेमाल, वेब एपीआई से अपने ऐप्लिकेशन के डेटा मॉडल में डेटा मैप करने के लिए किया हो. हालांकि, यह इससे कहीं ज़्यादा फ़्लेक्सिबल है.
इस गाइड में, हम देखेंगे कि Cloud Firestore से Swift टाइप में और इसके उलट डेटा मैप करने के लिए, Codable का इस्तेमाल कैसे किया जा सकता है.
Cloud Firestore से कोई दस्तावेज़ फ़ेच करने पर, आपके ऐप्लिकेशन को कुंजी/वैल्यू पेयर की डिक्शनरी मिलेगी. अगर एक से ज़्यादा दस्तावेज़ों को वापस करने वाले किसी ऑपरेशन का इस्तेमाल किया जाता है, तो डिक्शनरी की एक कैटगरी मिलेगी.
Swift में डिक्शनरी का सीधे तौर पर इस्तेमाल किया जा सकता है. साथ ही, इनमें कुछ ऐसी फ़्लेक्सिबिलिटी मिलती है जो आपके इस्तेमाल के उदाहरण के लिए ज़रूरी हो सकती है. हालांकि, इस तरीके से टाइप सुरक्षित नहीं होते. साथ ही, एट्रिब्यूट के नाम की स्पेलिंग गलत होने या आपकी टीम ने पिछले हफ़्ते जो नया फ़ीचर लॉन्च किया था उसके लिए जोड़े गए नए एट्रिब्यूट को मैप करना भूल जाने की वजह से, ऐसी गड़बड़ियां हो सकती हैं जिन्हें ठीक करना मुश्किल हो सकता है.
पहले, कई डेवलपर ने इन कमियों को दूर करने के लिए, एक आसान मैपिंग लेयर लागू की थी. इससे वे डिक्शनरी को Swift टाइप में मैप कर पाते थे. हालांकि, इनमें से ज़्यादातर लागू करने के तरीके, Cloud Firestore दस्तावेज़ों और आपके ऐप्लिकेशन के डेटा मॉडल के मिलते-जुलते टाइप के बीच मैपिंग को मैन्युअल तरीके से तय करने पर आधारित होते हैं.
Cloud Firestore में Swift के Codable API के लिए सहायता उपलब्ध है. इससे यह काम बहुत आसान हो जाता है:
- अब आपको मैपिंग कोड को मैन्युअल तरीके से लागू करने की ज़रूरत नहीं होगी.
- अलग-अलग नामों वाले एट्रिब्यूट को मैप करने का तरीका आसानी से तय किया जा सकता है.
- इसमें Swift के कई टाइप के लिए, पहले से सहायता मौजूद है.
- कस्टम टाइप को मैप करने के लिए, सहायता जोड़ना आसान है.
- सबसे अच्छी बात यह है कि आसान डेटा मॉडल के लिए, आपको मैपिंग कोड लिखने की ज़रूरत नहीं होगी.
डेटा मैप करना
Cloud Firestore दस्तावेज़ों में डेटा स्टोर करता है. ये दस्तावेज़, कुंजियों को वैल्यू से मैप करते हैं. किसी दस्तावेज़ से डेटा फ़ेच करने के लिए, हम DocumentSnapshot.data() को कॉल कर सकते हैं. इससे, फ़ील्ड के नामों को Any से मैप करने वाली डिक्शनरी मिलती है: func data() -> [String : Any]?.
इसका मतलब है कि हम हर फ़ील्ड को ऐक्सेस करने के लिए, Swift के सबस्क्रिप्ट सिंटैक्स का इस्तेमाल कर सकते हैं.
import FirebaseFirestore
#warning("DO NOT MAP YOUR DOCUMENTS MANUALLY. USE CODABLE INSTEAD.")
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
let id = document.documentID
let data = document.data()
let title = data?["title"] as? String ?? ""
let numberOfPages = data?["numberOfPages"] as? Int ?? 0
let author = data?["author"] as? String ?? ""
self.book = Book(id:id, title: title, numberOfPages: numberOfPages, author: author)
}
}
}
}
यह कोड लागू करने में आसान और सीधा लग सकता है. हालांकि, यह कोड कमज़ोर है. इसे बनाए रखना मुश्किल है और इसमें गड़बड़ियां होने की संभावना ज़्यादा होती है.
जैसा कि आप देख सकते हैं, हम दस्तावेज़ के फ़ील्ड के डेटा टाइप के बारे में अनुमान लगा रहे हैं. ये अनुमान सही भी हो सकते हैं और गलत भी.
ध्यान रखें कि स्कीमा न होने की वजह से, कलेक्शन में नया दस्तावेज़ आसानी से जोड़ा जा सकता है. साथ ही, किसी फ़ील्ड के लिए कोई दूसरा टाइप चुना जा सकता है. ऐसा हो सकता है कि आपने गलती से numberOfPages फ़ील्ड के लिए स्ट्रिंग चुन ली हो. इससे, मैपिंग से जुड़ी ऐसी गड़बड़ी हो सकती है जिसे ठीक करना मुश्किल हो. इसके अलावा, नया फ़ील्ड जोड़े जाने पर, आपको अपना मैपिंग कोड अपडेट करना होगा. यह काफ़ी मुश्किल काम है.
साथ ही, यह भी न भूलें कि हम Swift के मज़बूत टाइप सिस्टम का फ़ायदा नहीं ले रहे हैं. इस सिस्टम को Book की हर प्रॉपर्टी के लिए सही टाइप की जानकारी होती है.
Codable क्या है?
Apple के दस्तावेज़ के मुताबिक, Codable "एक ऐसा टाइप है जो खुद को बाहरी फ़ॉर्मैट में बदल सकता है और बाहरी फ़ॉर्मैट से खुद में बदल सकता है." असल में, Codable, Encodable और Decodable प्रोटोकॉल के लिए एक टाइप एलियास है. Swift टाइप को इस प्रोटोकॉल के मुताबिक बनाने पर, कंपाइलर उस कोड को सिंथेसाइज़ करेगा जिसकी मदद से, इस टाइप के इंस्टेंस को सीरियलाइज़ किए गए फ़ॉर्मैट (जैसे, JSON) से कोड में बदला जा सकेगा या कोड से सीरियलाइज़ किए गए फ़ॉर्मैट में बदला जा सकेगा.
किसी किताब के बारे में डेटा स्टोर करने के लिए, एक आसान टाइप इस तरह दिख सकता है:
struct Book: Codable {
var title: String
var numberOfPages: Int
var author: String
}
जैसा कि आप देख सकते हैं, टाइप को Codable के मुताबिक बनाने के लिए, बहुत कम बदलाव करने पड़ते हैं. हमें सिर्फ़ प्रोटोकॉल के मुताबिक बनाना पड़ा. इसके अलावा, कोई और बदलाव करने की ज़रूरत नहीं पड़ी.
अब हम किसी किताब को JSON ऑब्जेक्ट में आसानी से कोड में बदल सकते हैं:
do {
let book = Book(title: "The Hitchhiker's Guide to the Galaxy",
numberOfPages: 816,
author: "Douglas Adams")
let encoder = JSONEncoder()
let data = try encoder.encode(book)
}
catch {
print("Error when trying to encode book: \(error)")
}
किसी JSON ऑब्जेक्ट को Book इंस्टेंस में डिकोड करने का तरीका यहां दिया गया है:
let decoder = JSONDecoder()
let data = /* fetch data from the network */
let decodedBook = try decoder.decode(Book.self, from: data)
Codable का इस्तेमाल करके, Cloud Firestore दस्तावेज़ों में मौजूद आसान टाइप को मैप करना और उनसे डेटा मैप करना
Cloud Firestore कई तरह के डेटा टाइप के साथ काम करता है. इनमें आसान स्ट्रिंग से लेकर नेस्ट किए गए मैप तक शामिल हैं. इनमें से ज़्यादातर टाइप, Swift के बिल्ट-इन टाइप से सीधे तौर पर मेल खाते हैं. आइए, ज़्यादा मुश्किल डेटा टाइप के बारे में जानने से पहले, कुछ आसान डेटा टाइप को मैप करने का तरीका देखें.
Cloud Firestore दस्तावेज़ों को Swift टाइप में मैप करने के लिए, यह तरीका अपनाएं:
- पक्का करें कि आपने अपने प्रोजेक्ट में
FirebaseFirestoreफ़्रेमवर्क जोड़ा हो. इसके लिए, Swift Package Manager या CocoaPods का इस्तेमाल किया जा सकता है. - अपनी Swift फ़ाइल में
FirebaseFirestoreइंपोर्ट करें. - अपने टाइप को
Codableके मुताबिक बनाएं. - (ज़रूरी नहीं. अगर आपको
Listव्यू में टाइप का इस्तेमाल करना है, तो) अपने टाइप मेंidप्रॉपर्टी जोड़ें. साथ ही,@DocumentIDको यह बताने के लिए Cloud Firestore को इस्तेमाल करें कि इसे दस्तावेज़ आईडी में मैप करना है. हम इसके बारे में ज़्यादा जानकारी नीचे देंगे. - किसी दस्तावेज़ के रेफ़रंस को Swift टाइप में मैप करने के लिए,
documentReference.data(as: )का इस्तेमाल करें. - Swift टाइप से
Cloud Firestore दस्तावेज़ में डेटा मैप करने के लिए,
documentReference.setData(from: )का इस्तेमाल करें. - (ज़रूरी नहीं है, लेकिन हमारा सुझाव है कि आप इसे इस्तेमाल करें) गड़बड़ी ठीक करने की सुविधा को सही तरीके से लागू करें.
आइए, अपने Book टाइप को इसके हिसाब से अपडेट करें:
struct Book: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
}
यह टाइप पहले से ही कोड में बदला जा सकता था. इसलिए, हमें सिर्फ़ id प्रॉपर्टी जोड़नी पड़ी और इसे @DocumentID प्रॉपर्टी रैपर के साथ एनोटेट करना पड़ा.
किसी दस्तावेज़ को फ़ेच और मैप करने के लिए, पिछले कोड स्निपेट का इस्तेमाल करके, हम मैन्युअल तरीके से मैप करने वाले पूरे कोड को एक लाइन से बदल सकते हैं:
func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument { document, error in
if let error = error as NSError? {
self.errorMessage = "Error getting document: \(error.localizedDescription)"
}
else {
if let document = document {
do {
self.book = try document.data(as: Book.self)
}
catch {
print(error)
}
}
}
}
}
getDocument(as:) को कॉल करते समय, दस्तावेज़ का टाइप तय करके इसे और भी संक्षिप्त तरीके से लिखा जा सकता है. इससे मैपिंग की प्रोसेस पूरी हो जाएगी. साथ ही, मैप किए गए दस्तावेज़ या डिकोड करने में गड़बड़ी होने पर, Result टाइप मिलेगा:
private func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument(as: Book.self) { result in
switch result {
case .success(let book):
// A Book value was successfully initialized from the DocumentSnapshot.
self.book = book
self.errorMessage = nil
case .failure(let error):
// A Book value could not be initialized from the DocumentSnapshot.
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
किसी मौजूदा दस्तावेज़ को अपडेट करना, documentReference.setData(from: ) को कॉल करने जितना आसान है. Book इंस्टेंस को सेव करने के लिए, यहां गड़बड़ी ठीक करने की बुनियादी सुविधा वाला कोड दिया गया है:
func updateBook(book: Book) {
if let id = book.id {
let docRef = db.collection("books").document(id)
do {
try docRef.setData(from: book)
}
catch {
print(error)
}
}
}
नया दस्तावेज़ जोड़ते समय, Cloud Firestore दस्तावेज़ को नया आईडी असाइन करने का काम अपने-आप कर लेगा. यह सुविधा, ऐप्लिकेशन के ऑफ़लाइन होने पर भी काम करती है.
func addBook(book: Book) {
let collectionRef = db.collection("books")
do {
let newDocReference = try collectionRef.addDocument(from: self.book)
print("Book stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
आसान डेटा टाइप को मैप करने के अलावा, Cloud Firestore कई अन्य डेटा टाइप के साथ भी काम करता है. इनमें से कुछ स्ट्रक्चर्ड टाइप हैं. इनका इस्तेमाल करके, किसी दस्तावेज़ में नेस्ट किए गए ऑब्जेक्ट बनाए जा सकते हैं.
नेस्ट किए गए कस्टम टाइप
हमारे दस्तावेज़ों में मैप किए जाने वाले ज़्यादातर एट्रिब्यूट, आसान वैल्यू होते हैं. जैसे, किताब का टाइटल या लेखक का नाम. हालांकि, उन मामलों में क्या करें जब हमें कोई ज़्यादा कॉम्प्लेक्स ऑब्जेक्ट स्टोर करना हो? उदाहरण के लिए, हम किताब के कवर के यूआरएल को अलग-अलग रिज़ॉल्यूशन में स्टोर करना चाहें.
Cloud Firestore में ऐसा करने का सबसे आसान तरीका है कि मैप का इस्तेमाल किया जाए:

मिलते-जुलते Swift स्ट्रक्चर लिखते समय, हम इस बात का फ़ायदा ले सकते हैं कि Cloud Firestore यूआरएल के साथ काम करता है. यूआरएल वाला फ़ील्ड स्टोर करने पर, उसे स्ट्रिंग में बदल दिया जाएगा. साथ ही, स्ट्रिंग को यूआरएल में बदला जा सकेगा:
struct CoverImages: Codable {
var small: URL
var medium: URL
var large: URL
}
struct BookWithCoverImages: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var cover: CoverImages?
}
ध्यान दें कि हमने CoverImages नाम का स्ट्रक्चर कैसे तय किया है, जो
Cloud Firestore दस्तावेज़ में कवर मैप के लिए है. BookWithCoverImages पर कवर प्रॉपर्टी को वैकल्पिक के तौर पर मार्क करके, हम इस बात को मैनेज कर सकते हैं कि कुछ दस्तावेज़ों में कवर एट्रिब्यूट शामिल नहीं हो सकता.
अगर आपको यह जानना है कि डेटा फ़ेच करने या अपडेट करने के लिए कोई कोड स्निपेट क्यों नहीं है, तो आपको यह जानकर खुशी होगी कि Cloud Firestore से डेटा पढ़ने या उसमें डेटा लिखने के लिए, कोड में बदलाव करने की ज़रूरत नहीं है. यह सब, शुरुआती सेक्शन में लिखे गए कोड के साथ काम करता है.
श्रेणियां
कभी-कभी, हम किसी दस्तावेज़ में वैल्यू का कलेक्शन स्टोर करना चाहते हैं. किसी किताब के जॉनर इसका एक अच्छा उदाहरण हैं. The Hitchhiker's Guide to the Galaxy जैसी किताब, कई कैटगरी में आ सकती है. इस मामले में, "Sci-Fi" और "Comedy":

Cloud Firestore में, हम इसे वैल्यू की कैटगरी का इस्तेमाल करके मॉडल कर सकते हैं. यह सुविधा, कोड में बदले जा सकने वाले किसी भी टाइप (जैसे, String, Int वगैरह) के लिए उपलब्ध है. यहां बताया गया है कि हमारे Book मॉडल में जॉनर की कैटगरी कैसे जोड़ी जाती है:
public struct BookWithGenre: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var genres: [String]
}
यह सुविधा, कोड में बदले जा सकने वाले किसी भी टाइप के लिए उपलब्ध है. इसलिए, हम कस्टम टाइप का भी इस्तेमाल कर सकते हैं. मान लें कि हमें हर किताब के लिए टैग की सूची स्टोर करनी है. टैग के नाम के साथ-साथ, हम टैग का रंग भी स्टोर करना चाहेंगे. जैसे:

इस तरह से टैग स्टोर करने के लिए, हमें सिर्फ़ एक टैग को दिखाने के लिए 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]
}
दस्तावेज़ आईडी को मैप करने के बारे में खास जानकारी
अलग-अलग टाइप को मैप करने के बारे में जानने से पहले, आइए कुछ देर के लिए दस्तावेज़ आईडी को मैप करने के बारे में बात करते हैं.
हमने पिछले कुछ उदाहरणों में, अपने Cloud Firestore दस्तावेज़ों के दस्तावेज़ आईडी को अपने Swift टाइप की id प्रॉपर्टी में मैप करने के लिए, @DocumentID प्रॉपर्टी रैपर का इस्तेमाल किया था. यह कई वजहों से ज़रूरी है:
- इससे हमें यह जानने में मदद मिलती है कि उपयोगकर्ता के स्थानीय बदलाव करने पर, किस दस्तावेज़ को अपडेट करना है.
- SwiftUI की
Listके लिए, उसके एलिमेंट कोIdentifiableहोना ज़रूरी है. इससे, एलिमेंट को जोड़ने पर वे इधर-उधर नहीं होते.
यह ध्यान रखना ज़रूरी है कि @DocumentID के तौर पर मार्क किए गए एट्रिब्यूट को, दस्तावेज़ को वापस लिखते समय, Cloud Firestore के एन्कोडर से कोड में नहीं बदला जाएगा. ऐसा इसलिए है, क्योंकि दस्तावेज़ आईडी, दस्तावेज़ का एट्रिब्यूट नहीं होता. इसलिए, इसे दस्तावेज़ में लिखना एक गड़बड़ी होगी.
नेस्ट किए गए टाइप (जैसे, इस गाइड में पहले दिए गए उदाहरण में Book पर टैग की कैटगरी) के साथ काम करते समय, @DocumentID
प्रॉपर्टी जोड़ने की ज़रूरत नहीं होती. नेस्ट की गई प्रॉपर्टी, Cloud Firestore दस्तावेज़ का हिस्सा होती हैं और
वे अलग दस्तावेज़ नहीं होती हैं. इसलिए, उन्हें दस्तावेज़ आईडी की ज़रूरत नहीं होती.
तारीख और समय
Cloud Firestore में, तारीख और समय को मैनेज करने के लिए, बिल्ट-इन डेटा टाइप मौजूद है. साथ ही, Cloud Firestore में Codable के लिए सहायता उपलब्ध होने की वजह से, इनका इस्तेमाल करना आसान है.
आइए, इस दस्तावेज़ पर एक नज़र डालते हैं. इसमें सभी प्रोग्रामिंग भाषाओं की जननी, Ada के बारे में बताया गया है. इसका आविष्कार 1843 में हुआ था:

इस दस्तावेज़ को मैप करने के लिए, Swift टाइप इस तरह दिख सकता है:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
}
तारीख और समय के बारे में इस सेक्शन को, @ServerTimestamp के बारे में बात किए बिना पूरा नहीं किया जा सकता. यह प्रॉपर्टी रैपर, आपके ऐप्लिकेशन में टाइमस्टैंप से जुड़े काम करने के लिए बहुत काम का है.
किसी भी डिस्ट्रिब्यूटेड सिस्टम में, ऐसा हो सकता है कि अलग-अलग सिस्टम पर मौजूद घड़ियां, हर समय पूरी तरह सिंक न हों. आपको लग सकता है कि यह कोई बड़ी बात नहीं है. हालांकि, स्टॉक ट्रेड सिस्टम के लिए, थोड़ी सी भी आउट ऑफ़ सिंक घड़ी के असर के बारे में सोचें. ट्रेड को एक्ज़ीक्यूट करते समय, मिलीसेकंड का अंतर भी लाखों डॉलर का अंतर ला सकता है.
Cloud Firestore एट्रिब्यूट को इस तरह मैनेज करता है: अगर एट्रिब्यूट को स्टोर करते समय (उदाहरण के लिए, addDocument() का इस्तेमाल करके) उसकी वैल्यू nil है, तो Cloud Firestore फ़ील्ड में डेटाबेस में लिखते समय, मौजूदा सर्वर टाइमस्टैंप की वैल्यू डाल देगा.@ServerTimestamp अगर फ़ील्ड की वैल्यू nil
जब आप addDocument() या updateData() को कॉल करते हैं, तो Cloud Firestore एट्रिब्यूट की वैल्यू में कोई बदलाव नहीं करेगा. इस तरह, createdAt और lastUpdatedAt जैसे फ़ील्ड को लागू करना आसान है.
जियोपॉइंट
हमारे ऐप्लिकेशन में, जियोलोकेशन हर जगह मौजूद हैं. इन्हें स्टोर करके, कई शानदार फ़ीचर उपलब्ध कराए जा सकते हैं. उदाहरण के लिए, किसी टास्क के लिए जगह की जानकारी स्टोर करना काम का हो सकता है. इससे, आपके गंतव्य पर पहुंचने पर, आपका ऐप्लिकेशन आपको टास्क के बारे में याद दिला सकता है.
Cloud Firestore में, GeoPoint नाम का बिल्ट-इन डेटा टाइप मौजूद है. इसमें किसी भी जगह का
देशांतर और अक्षांश स्टोर किया जा सकता है. जगह की जानकारी को किसी
Cloud Firestore दस्तावेज़ से मैप करने या उसमें मैप करने के लिए, हम GeoPoint टाइप का इस्तेमाल कर सकते हैं:
struct Office: Codable {
@DocumentID var id: String?
var name: String
var location: GeoPoint
}
Swift में, इसके लिए CLLocationCoordinate2D टाइप का इस्तेमाल किया जाता है. हम इन दोनों टाइप के बीच, इस ऑपरेशन से मैप कर सकते हैं:
CLLocationCoordinate2D(latitude: office.location.latitude,
longitude: office.location.longitude)
भौगोलिक जगह के हिसाब से दस्तावेज़ों की क्वेरी करने के बारे में ज़्यादा जानने के लिए, इस समाधान गाइड को देखें.
एनम्स
एनम्स, शायद Swift की सबसे कम आंकी जाने वाली भाषा की सुविधाओं में से एक हैं. इनमें काफ़ी कुछ है. एनम्स का इस्तेमाल, किसी चीज़ की अलग-अलग स्थितियों को मॉडल करने के लिए किया जाता है. उदाहरण के लिए, हम लेखों को मैनेज करने के लिए कोई ऐप्लिकेशन लिख रहे हैं. किसी लेख की स्थिति को ट्रैक करने के लिए, हम Status नाम के एनम का इस्तेमाल करना चाहें:
enum Status: String, Codable {
case draft
case inReview
case approved
case published
}
Cloud Firestore एनम्स के साथ नेटिव तौर पर काम नहीं करता. इसका मतलब है कि यह वैल्यू के सेट को लागू नहीं कर सकता. हालांकि, हम इस बात का फ़ायदा ले सकते हैं कि एनम्स को टाइप किया जा सकता है. साथ ही, कोड में बदला जा सकने वाला टाइप चुना जा सकता है. इस उदाहरण में, हमने String चुना है. इसका मतलब है कि
दस्तावेज़ में स्टोर किए जाने पर, Cloud Firestore सभी एनम वैल्यू को स्ट्रिंग में मैप किया जाएगा और स्ट्रिंग से एनम वैल्यू में मैप किया जाएगा.
साथ ही, Swift में कस्टम रॉ वैल्यू के लिए सहायता उपलब्ध है. इसलिए, हम यह भी तय कर सकते हैं कि कौनसी वैल्यू, किस एनम केस को रेफ़र करती हैं. उदाहरण के लिए, अगर हमने Status.inReview केस को "in review" के तौर पर स्टोर करने का फ़ैसला किया है, तो हम ऊपर दिए गए एनम को इस तरह अपडेट कर सकते हैं:
enum Status: String, Codable {
case draft
case inReview = "in review"
case approved
case published
}
मैपिंग को पसंद के मुताबिक बनाना
कभी-कभी, उन Cloud Firestore दस्तावेज़ों के एट्रिब्यूट के नाम, जिन्हें हमें मैप करना है, Swift में हमारे डेटा मॉडल की प्रॉपर्टी के नामों से मेल नहीं खाते. उदाहरण के लिए, हमारे किसी सहकर्मी, जो Python डेवलपर है, ने अपने सभी एट्रिब्यूट के नामों के लिए snake_case चुनने का फ़ैसला किया.
चिंता न करें: Codable आपकी मदद करेगा!
ऐसे मामलों के लिए, हम CodingKeys का इस्तेमाल कर सकते हैं. यह एक एनम है, जिसे कोड में बदले जा सकने वाले स्ट्रक्चर में जोड़ा जा सकता है. इससे यह तय किया जा सकता है कि कुछ एट्रिब्यूट को कैसे मैप किया जाएगा.
इस दस्तावेज़ पर विचार करें:

इस दस्तावेज़ को ऐसे स्ट्रक्चर में मैप करने के लिए जिसमें String टाइप की name प्रॉपर्टी है, हमें 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
}
}
डिफ़ॉल्ट रूप से, Codable API, Cloud Firestore दस्तावेज़ों के एट्रिब्यूट के नाम तय करने के लिए, हमारे Swift टाइप की प्रॉपर्टी के नामों का इस्तेमाल करेगा जिन्हें हम मैप करने की कोशिश कर रहे हैं. इसलिए, जब तक एट्रिब्यूट के नाम मेल खाते हैं, तब तक हमें अपने कोड में बदले जा सकने वाले टाइप में CodingKeys जोड़ने की ज़रूरत नहीं है. हालांकि, किसी खास टाइप के लिए CodingKeys का इस्तेमाल करने पर, हमें उन सभी प्रॉपर्टी के नाम जोड़ने होंगे जिन्हें हमें मैप करना है.
ऊपर दिए गए कोड स्निपेट में, हमने id प्रॉपर्टी तय की है. हो सकता है कि हम इसका इस्तेमाल, SwiftUI 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
}
}
कभी-कभी, हम
Cloud Firestore दस्तावेज़ में खाली एट्रिब्यूट लिखना चाहें. Swift में, वैल्यू न होने की जानकारी देने के लिए, ऑप्शनल का इस्तेमाल किया जाता है. साथ ही, Cloud Firestore में null वैल्यू के लिए भी सहायता उपलब्ध है.
हालांकि, nil वैल्यू वाले ऑप्शनल को कोड में बदलने का डिफ़ॉल्ट तरीका है कि उन्हें छोड़ दिया जाए. @ExplicitNull से हमें यह तय करने में मदद मिलती है कि Swift
के ऑप्शनल को कोड में बदलते समय, उन्हें कैसे मैनेज किया जाए. किसी ऑप्शनल प्रॉपर्टी को
@ExplicitNull के तौर पर फ़्लैग करके, हम Cloud Firestore को यह बता सकते हैं कि अगर इस प्रॉपर्टी में nil की वैल्यू है, तो इसे
दस्तावेज़ में null वैल्यू के साथ लिखा जाए.
रंगों को मैप करने के लिए, कस्टम एन्कोडर और डिकोडर का इस्तेमाल करना
Codable की मदद से डेटा मैप करने के बारे में, आखिरी विषय के तौर पर, आइए कस्टम एन्कोडर और डिकोडर के बारे में जानते हैं. इस सेक्शन में, नेटिव Cloud Firestore डेटा टाइप के बारे में नहीं बताया गया है. हालांकि, कस्टम एन्कोडर और डिकोडर आपके Cloud Firestore ऐप्लिकेशन में बहुत काम के होते हैं.
"मैं रंगों को कैसे मैप करूं" डेवलपर के सबसे ज़्यादा पूछे जाने वाले सवालों में से एक है, सिर्फ़ Cloud Firestore के लिए ही नहीं, बल्कि Swift और JSON के बीच मैपिंग के लिए भी पूछा जाता है. इसके कई समाधान मौजूद हैं. हालांकि, इनमें से ज़्यादातर JSON पर फ़ोकस करते हैं. साथ ही, इनमें से लगभग सभी, रंगों को आरजीबी कॉम्पोनेंट से बने नेस्ट किए गए डिक्शनरी के तौर पर मैप करते हैं.
ऐसा लगता है कि इसका कोई बेहतर और आसान समाधान होना चाहिए. हम वेब कलर (या ज़्यादा सटीक तौर पर, सीएसएस हेक्स कलर नोटेशन) का इस्तेमाल क्यों नहीं करते? इनका इस्तेमाल करना आसान है. ये सिर्फ़ एक स्ट्रिंग होते हैं. साथ ही, इनमें पारदर्शिता के लिए भी सहायता उपलब्ध है!
Swift के Color को उसकी हेक्स वैल्यू में मैप करने के लिए, हमें एक Swift एक्सटेंशन बनाना होगा. इससे Color में Codable जोड़ा जा सकेगा.
extension Color {
init(hex: String) {
let rgba = hex.toRGBA()
self.init(.sRGB,
red: Double(rgba.r),
green: Double(rgba.g),
blue: Double(rgba.b),
opacity: Double(rgba.alpha))
}
//... (code for translating between hex and RGBA omitted for brevity)
}
extension Color: Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let hex = try container.decode(String.self)
self.init(hex: hex)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(toHex)
}
}
decoder.singleValueContainer() का इस्तेमाल करके, हम String को उसके Color के बराबर वैल्यू में डिकोड कर सकते हैं. इसके लिए, आरजीबीए कॉम्पोनेंट को नेस्ट करने की ज़रूरत नहीं होती. इसके अलावा, इन वैल्यू का इस्तेमाल, अपने ऐप्लिकेशन के वेब यूज़र इंटरफ़ेस (यूआई) में किया जा सकता है. इसके लिए, पहले इन्हें बदलने की ज़रूरत नहीं होती!
इससे, टैग को मैप करने के लिए कोड को अपडेट किया जा सकता है. इससे, टैग के रंगों को सीधे तौर पर मैनेज करना आसान हो जाता है. इसके लिए, अपने ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) कोड में उन्हें मैन्युअल तरीके से मैप करने की ज़रूरत नहीं होती:
struct Tag: Codable, Hashable {
var title: String
var color: Color
}
struct BookWithTags: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var tags: [Tag]
}
गड़बड़ियों को ठीक करना
ऊपर दिए गए कोड स्निपेट में, हमने जान-बूझकर गड़बड़ी ठीक करने की सुविधा को कम से कम रखा है. हालांकि, प्रोडक्शन ऐप्लिकेशन में, आपको यह पक्का करना होगा कि गड़बड़ियों को सही तरीके से ठीक किया जाए.
यहां एक कोड स्निपेट दिया गया है. इसमें बताया गया है कि आपको किन गड़बड़ियों का सामना करना पड़ सकता है और उन्हें कैसे ठीक किया जा सकता है:
class MappingSimpleTypesViewModel: ObservableObject {
@Published var book: Book = .empty
@Published var errorMessage: String?
private var db = Firestore.firestore()
func fetchAndMap() {
fetchBook(documentId: "hitchhiker")
}
func fetchAndMapNonExisting() {
fetchBook(documentId: "does-not-exist")
}
func fetchAndTryMappingInvalidData() {
fetchBook(documentId: "invalid-data")
}
private func fetchBook(documentId: String) {
let docRef = db.collection("books").document(documentId)
docRef.getDocument(as: Book.self) { result in
switch result {
case .success(let book):
// A Book value was successfully initialized from the DocumentSnapshot.
self.book = book
self.errorMessage = nil
case .failure(let error):
// A Book value could not be initialized from the DocumentSnapshot.
switch error {
case DecodingError.typeMismatch(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self.errorMessage = "\(error.localizedDescription): \(key)"
default:
self.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
}
}
}
}
लाइव अपडेट में गड़बड़ियों को ठीक करना
पिछले कोड स्निपेट में, किसी एक दस्तावेज़ को फ़ेच करते समय होने वाली गड़बड़ियों को ठीक करने का तरीका बताया गया है. Cloud Firestore, डेटा को एक बार फ़ेच करने के अलावा, Cloud Firestore भी आपके ऐप्लिकेशन को अपडेट भी डिलीवर करता है. इसके लिए, स्नैपशॉट लिसनर का इस्तेमाल किया जाता है. हम किसी कलेक्शन (या क्वेरी) पर स्नैपशॉट लिसनर रजिस्टर कर सकते हैं. इसके बाद, Cloud Firestore अपडेट होने पर हमारे लिसनर को कॉल करेगा.
यहां एक कोड स्निपेट दिया गया है. इसमें बताया गया है कि स्नैपशॉट लिसनर को कैसे रजिस्टर किया जाता है, Codable का इस्तेमाल करके डेटा को कैसे मैप किया जाता है, और होने वाली गड़बड़ियों को कैसे ठीक किया जाता है. इसमें यह भी बताया गया है कि कलेक्शन में नया दस्तावेज़ कैसे जोड़ा जाता है. जैसा कि आप देखेंगे, मैप किए गए दस्तावेज़ों को रखने वाली स्थानीय कलेक्शन को खुद से अपडेट करने की ज़रूरत नहीं है. यह काम, स्नैपशॉट लिसनर में मौजूद कोड से हो जाता है.
class MappingColorsViewModel: ObservableObject {
@Published var colorEntries = [ColorEntry]()
@Published var newColor = ColorEntry.empty
@Published var errorMessage: String?
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("colors")
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.errorMessage = "No documents in 'colors' collection"
return
}
self?.colorEntries = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: ColorEntry.self) }
switch result {
case .success(let colorEntry):
if let colorEntry = colorEntry {
// A ColorEntry value was successfully initialized from the DocumentSnapshot.
self?.errorMessage = nil
return colorEntry
}
else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
self?.errorMessage = "Document doesn't exist."
return nil
}
case .failure(let error):
// A ColorEntry value could not be initialized from the DocumentSnapshot.
switch error {
case DecodingError.typeMismatch(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self?.errorMessage = "\(error.localizedDescription): \(key)"
default:
self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
return nil
}
}
}
}
}
func addColorEntry() {
let collectionRef = db.collection("colors")
do {
let newDocReference = try collectionRef.addDocument(from: newColor)
print("ColorEntry stored with new document reference: \(newDocReference)")
}
catch {
print(error)
}
}
}
आगे बढ़ें और Codable का इस्तेमाल करें!
Swift का Codable API, सीरियलाइज़ किए गए फ़ॉर्मैट से आपके ऐप्लिकेशन के डेटा मॉडल में और उससे डेटा मैप करने का एक असरदार और फ़्लेक्सिबल तरीका है. इस गाइड में, आपने देखा कि Cloud Firestore को डेटास्टोर के तौर पर इस्तेमाल करने वाले ऐप्लिकेशन में, इसका इस्तेमाल करना कितना आसान है.
हमने आसान डेटा टाइप वाले बुनियादी उदाहरण से शुरुआत की. इसके बाद, हमने डेटा मॉडल की जटिलता को धीरे-धीरे बढ़ाया. इस दौरान, हम मैपिंग करने के लिए Codable और Firebase के लागू करने के तरीके पर भरोसा कर पाए.
Codable के बारे में ज़्यादा जानने के लिए, मैं आपको ये संसाधन देखने का सुझाव देता/देती हूं:
- जॉन संडेल ने Codable की बुनियादी बातों के बारे में एक अच्छा लेख लिखा है.
- अगर आपको किताबें पढ़ने में दिलचस्पी है, तो मैट की Flight School Guide to Swift Codable देखें.
- आखिर में, डन्नी वॉल्स ने Codable के बारे में पूरी सीरीज़ बनाई है.
हमने Cloud Firestore दस्तावेज़ों को मैप करने के लिए, एक पूरी गाइड बनाने की पूरी कोशिश की है. हालांकि, यह पूरी नहीं है. हो सकता है कि आपने अपने टाइप को मैप करने के लिए, अन्य रणनीतियों का इस्तेमाल किया हो. नीचे दिए गए सुझाव/राय दें या शिकायत करें बटन का इस्तेमाल करके, हमें बताएं कि अन्य टाइप के Cloud Firestore डेटा को मैप करने या Swift में डेटा दिखाने के लिए, किन रणनीतियों का इस्तेमाल किया जाता है.
Cloud Firestore में Codable के लिए सहायता उपलब्ध है. इसलिए, इसका इस्तेमाल न करने की कोई वजह नहीं है.