تمكننا واجهة برمجة تطبيقات Codable لـ Swift، والتي تم تقديمها في Swift 4، من الاستفادة من قوة برنامج التحويل البرمجي لتسهيل تعيين البيانات من التنسيقات المتسلسلة إلى Swift الأنواع.
من المحتمل أنّك كنت تستخدم ميزة Codable لربط البيانات من Web API ببيانات تطبيقك. (والعكس صحيح)، لكنها أكثر مرونة من ذلك بكثير.
في هذا الدليل، سنلقي نظرة على كيفية استخدام Codable لتعيين البيانات من Cloud Firestore إلى أنواع Swift والعكس صحيح.
عند استرجاع مستند من Cloud Firestore، سيتلقى تطبيقك قاموس أزواج المفتاح/القيمة (أو صفيف من القواميس، إذا كنت تستخدم أحد العمليات التي تعرض مستندات متعددة).
الآن، يمكنك بالتأكيد الاستمرار في استخدام القواميس في Swift مباشرةً، وتوفر بعض المرونة الكبيرة التي قد تكون بالضبط ما تتطلبه حالة الاستخدام. ومع ذلك، هذه الطريقة ليست آمنة ومن السهل تقديمها أخطاء يصعُب تتبُّعها عن طريق الخطأ الإملائي في أسماء السمات أو نسيان تعيين السمة الجديدة التي أضافها فريقك عندما قام بشحن هذه الميزة الجديدة والمثيرة الأسبوع الماضي.
في الماضي، عالج العديد من المطوّرين أوجه القصور هذه عن طريق وتنفيذ طبقة تخطيط بسيطة تسمح لهم بتعيين القواميس أنواع Swift. ولكن مرة أخرى، تعتمد معظم عمليات التنفيذ هذه على لتحديد التعيين بين مستندات Cloud Firestore الأنواع المقابلة لنموذج بيانات تطبيقك.
مع دعم Cloud Firestore لواجهة برمجة تطبيقات Codable API من Swift، سيصبح الأمر أسهل:
- لن تحتاج إلى تنفيذ أي رمز ربط يدويًا بعد الآن.
- من السهل تحديد كيفية تعيين السمات بأسماء مختلفة.
- ويحتوي على توافق ضمني للعديد من أنواع بطاقات 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
، مما ينتج عنه
في مشكلة تصميم يصعب العثور عليها. سيكون عليك أيضًا تعديل عملية الربط
الرمز عند إضافة حقل جديد، ويكون هذا أمرًا مرهقًا إلى حد ما.
ودعنا لا ننسى أننا لا نستفيد من نوع سويفت القو��
��ل���� ��عر�� ��ال��بط النوع الصحيح لكل خاصية من خصائص
Book
ما هو Codable، على أي حال؟
ووفقًا لوثائق Apple، فإن Codable هو "نوع يمكنه تحويل نفسه داخل وخارج تمثيل خارجي". في الواقع، Codable هو نوع بديل لبروتوكولات Encodable والبروتوكولات الارتكازية. من خلال مطابقة نوع 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)
الربط من وإلى أنواع بسيطة في مستندات Cloud Firestore
باستخدام Codable
تدعم Cloud Firestore مجموعة واسعة من أنواع البيانات، بدءًا من البيانات السلاسل إلى خرائط متداخلة. ويتجاوب معظمها بشكل مباشر مع تطبيق Swift الأنواع. لنلقِ نظرة أولاً على ربط بعض أنواع البيانات البسيطة قبل أن نتعمق في إلى الأكثر تعقيدًا.
لربط مستندات Cloud Firestore بأنواع Swift، يُرجى اتّباع الخطوات التالية:
- تأكَّد من أنّك أضفت إطار عمل
FirebaseFirestore
إلى مشروعك. يمكنك استخدام إما مدير حزم Swift أو CocoaPods القيام بذلك. - قم باستيراد
FirebaseFirestore
إلى ملف Swift. - يجب مطابقة نوعك مع
Codable
. - (اختياري، إذا كنت تريد استخدام النوع في طريقة عرض
List
) أضِفid
على حسب نوعك، واستخدام@DocumentID
لإبلاغ Cloud Firestore وتعيين ذلك إلى معرف الوثيقة. وسنناقش هذا الموضوع بشكل أكثر تفصيلاً أدناه. - استخدام "
documentReference.data(as: )
" لربط مرجع المستند ببطاقة Swift الكتابة. - استخدِم "
documentReference.setData(from: )
" لربط البيانات من أنواع Swift بـ مستند Cloud Firestore - (اختياري، ولكننا ننصح به بشدّة) اتّخاذ الإجراءات المناسبة للتعامل مع الأخطاء.
لنعدّل نوع 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 أيضًا من أنواع البيانات الأخرى، بعضها عبارة عن أنواع مهيكلة يمكنك استخدامها لإنشاء كائنات متداخلة داخل مستند.
الأنواع المخصّصة المتداخلة
معظم السمات التي نريد تعيينها في مستنداتنا هي قيم بسيطة، مثل عنوان الكتاب أو اسم المؤلف. لكن ماذا عن تلك الحالات التي نحتاج فيها تخزن كائنًا أكثر تعقيدًا؟ على سبيل المثال، قد نرغب في تخزين عناوين URL غلاف الكتاب بدرجات دقة مختلفة.
أسهل طريقة لإجراء ذلك في Cloud Firestore هي استخدام خريطة:
عند كتابة هيكل Swift المقابل، يمكننا الاستفادة من حقيقة أن تتوافق Cloud Firestore مع عناوين URL، وعند تخزين حقل يحتوي على عنوان URL، سيتم تحويلها إلى سلسلة والعكس صحيح:
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: كل هذا يتوافق مع الرمز الذي مكتوبة في القسم الأولي.
الصفائف
في بعض الأحيان، نريد تخزين مجموعة من القيم في مستند. أنواع كتاب هو مثال جيد: كتاب مثل دليل المسافر إلى المجرة ضمن عدة فئات — وهي في هذه الحالة "خيال علمي" و "كوميديا":
في 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
}
وبهذه الطريقة، يمكننا تخزين مصفوفة من Tags
في مستندات Book
.
struct BookWithTags: Codable {
@DocumentID var id: String?
var title: String
var numberOfPages: Int
var author: String
var tags: [Tag]
}
نبذة عن تعيين معرّفات المستندات
قبل أن ننتقل إلى تعيين المزيد من الأنواع، لنتحدث عن ربط معرفات المستندات للحظة.
لقد استخدمنا برنامج تضمين السمة @DocumentID
في بعض الأمثلة السابقة.
لربط معرّف المستند لمستندات Cloud Firestore بالموقع الإلكتروني id
من أنواع Swift. وهذا أمر مهم لعدة أسباب:
- يساعدنا في معرفة المستند الذي يجب تحديثه في حالة إجراء المستخدم محليًا التغييرات.
- يتطلب
List
في SwiftUI أن تكون عناصرهIdentifiable
من أجل تمنع العناصر من التنقل عند إدراجها.
تجدر الإشارة إلى أنّ السمة التي تحمل علامة @DocumentID
لن يتم تصنيفها
تم ترميزه بواسطة برنامج ترميز Cloud Firestore عند كتابة المستند مرة أخرى. هذا هو
لأن معرف المستند ليس سمة للمستند نفسه - لذلك
فإن كتابته في الوثيقة سيكون خطأ.
عند العمل مع أنواع مدمَجة (مثل مصفوفة العلامات على Book
في
المثال السابق في هذا الدليل)، ليس من الضروري إضافة@DocumentID
: الخصائص المتداخلة هي جزء من مستند Cloud Firestore،
ألا تشكل وثيقة منفصلة. وبالتالي، لا يحتاجون إلى معرّف مستند.
التواريخ والأوقات
تحتوي Cloud Firestore على نوع بيانات مدمج للتعامل مع التواريخ والأوقات، وبفضل دعم Cloud Firestore لـ Codable، من السهل واستخدامها.
لنلقِ نظرة على هذا المستند الذي يعرض الأم للغات البرمجة "آدا" التي ظهرت في عام 1843:
قد يبدو نوع Swift لتعيين هذا المستند كما يلي:
struct ProgrammingLanguage: Codable {
@DocumentID var id: String?
var name: String
var year: Date
}
لا يمكننا مغادرة هذا القسم المتعلّق بالتواريخ والأوقات بدون إجراء محادثة.
حوالي @ServerTimestamp
. يعد برنامج تضمين الخاصية هذا قوة عندما يتعلق الأمر
التعامل مع الطوابع الزمنية في تطبيقك
ففي أي نظام موزّع، من المحتمل أن تكون الساعات على الأنظمة الفردية في مزامنة تامة طوال الوقت. قد تعتقد أن هذا ليس كبيرًا ولكن تخيل الآثار المترتبة على عدم تزامن الساعة إلى حد ما مع نظام تجارة الأسهم: حتى الانحراف بالمللي ثانية قد ينتج عنه فرق بملايين الدولارات عند تنفيذ صفقة.
تعالج Cloud Firestore السمات التي تم وضع علامة @ServerTimestamp
عليها باعتبارها
التالية: إذا كانت السمة هي nil
عند تخزينها (باستخدام addDocument()
، إذا
سبيل المثال)، ستقوم Cloud Firestore بملء الحقل بالخادم الحالي
الطابع الزمني وقت كتابته في قاعدة البيانات. إذا لم يكن الحقل 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
) باعتبارها "قيد المراجعة"، يمكننا فقط تعديل التعداد أعلاه
التالي:
enum Status: String, Codable {
case draft
case inReview = "in review"
case approved
case published
}
تخصيص عملية الربط
في بعض الأحيان، قد تتضمن أسماء السمات في مستندات Cloud Firestore التي نرغب في مع أسماء الخصائص في نموذج البيانات الخاص بنا في Swift. على سبيل المثال، قد يكون أحد زملاء العمل مطوّر برامج بايثون، وقرر اختر snake_case لجميع أسماء سماتها.
لا داعي للقلق: ساعِدنا في استخدام Codable.
في مثل هذه الحالات، يمكننا الاستفادة من CodingKeys
. هذا تعداد يمكننا
إضافتها إلى هيكل قابل للترميز لتحديد كيفية تعيين سمات معينة.
ضع في اعتبارك هذا المستند:
لربط هذا المستند ببنية تحتوي على خاصية اسم من النوع String
، يجب إنشاء
لإضافة تعداد CodingKeys
إلى بنية ProgrammingLanguage
، وتحديد
اسم السمة في المستند:
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 أسماء الخصائص الخاصة بأنواع Swift لدينا من أجل
تحديد أسماء السمات في مستندات Cloud Firestore التي نحاول
على الخريطة. ف طالما أنّ أسماء السمات متطابقة، لا داعي لإضافة
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 تعتمد سويفت مفهوم الاختيارات للإشارة إلى
بدون قيمة، ويتيح Cloud Firestore أيضًا قيم null
.
ومع ذلك، فإن السلوك التلقائي لخيارات الترميز التي لها قيمة nil
هو
لحذفها فقط. تمنحنا @ExplicitNull
بعض التحكم في كيفية
اختيارية عند تشفيرها: من خلال وضع علامة على إحدى الخصائص الاختيارية على أنها
@ExplicitNull
، يمكننا الطلب من Cloud Firestore كتابة هذه السمة في
بقيمة فارغة إذا كان يحتوي على القيمة nil
.
استخدام برنامج ترميز وبرنامج فك ترميز مخصّصَين لربط الألوان
وكموضوع أخير في تغطيتنا لبيانات الخرائط باستخدام Codable، دعنا نتعرف على برامج ترميز وأجهزة فك الترميز المخصّصة لا يتناول هذا القسم لغة بيانات Cloud Firestore، لكنّ برامج الترميز وفك الترميز المخصّصة مفيدة على نطاق واسع في تطبيقات Cloud Firestore.
"كيف يمكنني تعيين الألوان" أحد الأسئلة الأكثر شيوعًا التي يطرحها المطوّرون، ليس فقط مع Cloud Firestore، بل أيضًا للتعيين بين Swift وJSON أيضًا. هناك الكثير من الحلول المتاحة، لكن معظمها يركز على JSON، وترسم جميعها الألوان كقاموس متداخل يتكون من نموذج أحمر أخضر أزرق (RGB) والمكونات.
يبدو أنّه يجب توفير حلّ أفضل وأبسط. لماذا لا نستخدم ألوان الويب؟ (أو لنكون أكثر تحديدًا، تدوين اللون الست عشري CSS) - إنها سهلة الاستخدام (في الأساس سلسلة)، وهي تدعم الشفافية!
لربط Color
بعلامة Swift بقيمة سداسية عشرية، نحتاج إلى إنشاء رمز Swift
الإضافة التي تضيف Codable إلى اللغة 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()
، يمكننا فك ترميز String
Color
مكافئ، بدون الحاجة إلى دمج مكوّنات RGBA بالإضافة إلى ذلك، يمكنك
استخدام هذه القيم في واجهة مستخدم الويب لتطبيقك، بدون الحاجة إلى تحويلها
أَوَّلًا
باستخدام ذلك، يمكننا تحديث التعليمات البرمجية لتحديد العلامات، مما يسهل التعامل مع علامات الألوان مباشرةً بدلاً من ربطها يدويًا من خلال رمز واجهة المستخدم في التطبيق:
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 بمستمعنا عند توفّر تحديث.
فيما يلي مقتطف رمز يوضح كيفية تسجيل أداة معالجة اللقطات، وبيانات الخريطة باستخدام 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!
توفر واجهة برمجة تطبيقات Codable لـ Swift طريقة قوية ومرنة لتعيين البيانات من من وإلى نموذج بيانات التطبيقات. في هذا الدليل، رأيتم مدى سهولة الاستخدام في التطبيقات التي تستخدم Cloud Firestore مخزن البيانات.
بدءًا من مثال أساسي يتضمن أنواع بيانات بسيطة، أدى إلى زيادة تعقيد نموذج البيانات، مع قدرته على الاعتماد دائمًا على تنفيذ الترميز وFirebase لإجراء التعيين لنا.
لمزيد من التفاصيل حول ميزة Codable، أقترح الاطّلاع على المراجع التالية:
- يمتلك "جون سونديل" مقالة رائعة حول أساسيات الترميز.
- إذا كنت تفضّل قراءة الكتب، يمكنك الاطّلاع على مقالة دليل مدرسة الطيران لاستخدام أداة Swift Codable من "مات".
- وأخيرًا، لدى دوني Wals سلسلة كاملة حول Codable.
على الرغم من أننا بذلنا قصارى جهدنا لتجميع دليل شامل لرسم الخرائط مستندات Cloud Firestore هذه ليست شاملة، وربما تستخدم واستراتيجيات أخرى لتعيين أنواعك. باستخدام الزر إرسال الملاحظات أدناه، عليك إعلامنا بالاستراتيجيات التي تستخدمها لرسم أنواع أخرى من بيانات Cloud Firestore أو تمثيل البيانات في Swift.
لا يوجد سبب لعدم استخدام دعم Codable من Cloud Firestore.