(اختياري) إنشاء نموذج أولي وإجراء اختبار باستخدام Firebase Local Emulator Suite
قبل التحدّث عن كيفية قراءة تطبيقك من Realtime Database والكتابة فيه، لنقدّم مجموعة من الأدوات التي يمكنك استخدامها لإنشاء نموذج أولي لوظائف Realtime Database واختبارها: Firebase Local Emulator Suite. إذا كنت تجرب نماذج بيانات مختلفة أو تعمل على تحسين قواعد الأمان أو تحاول العثور على الطريقة الأكثر فعالية من حيث التكلفة للتفاعل مع الخادم الخلفي، يمكن أن يكون العمل محليًا بدون نشر الخدمات المباشرة فكرة رائعة.
يُعدّ برنامج محاكاة Realtime Database جزءًا من Local Emulator Suite، ما يتيح لتطبيقك التفاعل مع محتوى قاعدة البيانات التي تمت محاكاتها وإعداداتها، بالإضافة إلى موارد مشروعك التي تمت محاكاتها (الدوال وقواعد الأمان وقواعد البيانات الأخرى) بشكل اختياري.
لا يتطلّب استخدام برنامج محاكاة Realtime Database سوى بضع خطوات:
- إضافة سطر من الرمز إلى إعدادات الاختبار في تطبيقك للاتصال ببرنامج المحاكاة
- من جذر دليل مشروع على جهاز المستخدم المحلي، تنفيذ الأمر
firebase emulators:start. - إجراء طلبات من الرمز الأولي لتطبيقك باستخدام حزمة تطوير برامج (SDK) لمنصة Realtime Database كالمعتاد، أو باستخدام Realtime Database REST API.
يتوفّر شرح تفصيلي يتضمّن وRealtime Database وCloud Functions. عليك أيضًا الاطّلاع على Local Emulator Suite مقدّمة.
الحصول على FIRDatabaseReference
لقراءة البيانات من قاعدة البيانات أو الكتابة فيها، تحتاج إلى مثيل من FIRDatabaseReference:
Swift
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
كتابة البيانات
تتناول هذه المستندات أساسيات قراءة بيانات Firebase وكتابتها.
تتم كتابة بيانات Firebase في مرجع Database واستردادها من خلال إرفاق متتبِّع غير متزامن بالمرجع. يتم تفعيل المستمع مرة واحدة للحالة الأولية للبيانات ومرة أخرى في أي وقت تتغيّر فيه البيانات.
عمليات الكتابة الأساسية
بالنسبة إلى عمليات الكتابة الأساسية، يمكنك استخدام setValue لحفظ البيانات في مرجع محدّد، ما يؤدي إلى استبدال أي بيانات حالية في هذا المسار. يمكنك استخدام هذه الطريقة لإجراء ما يلي:
- تمرير الأنواع التي تتطابق مع أنواع JSON المتاحة على النحو التالي:
NSStringNSNumberNSDictionaryNSArray
على سبيل المثال، يمكنك إضافة مستخدم باستخدام setValue على النحو التالي:
Swift
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
يؤدي استخدام setValue بهذه الطريقة إلى الكتابة فوق البيانات في الموقع الجغرافي المحدّد، بما في ذلك أي عُقد فرعية. ومع ذلك، لا يزال بإمكانك تعديل عُقدة فرعية بدون إعادة كتابة الكائن بأكمله. إذا أردت السماح للمستخدمين بتعديل ملفاتهم الشخصية، يمكنك تعديل اسم المستخدم على النحو التالي:
Swift
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
قراءة البيانات
قراءة البيانات من خلال الاستماع إلى أحداث القيمة
لقراءة البيانات في مسار والاستماع إلى التغييرات، استخدِم observeEventType:withBlock من FIRDatabaseReference لمراقبة أحداث FIRDataEventTypeValue.
| نوع الحدث | الاستخدام المعتاد |
|---|---|
FIRDataEventTypeValue |
قراءة المحتوى الكامل لمسار والاستماع إلى التغييرات التي تطرأ عليه |
يمكنك استخدام حدث FIRDataEventTypeValue لقراءة البيانات في مسار معيّن، كما تظهر في وقت الحدث. يتم تفعيل هذه الطريقة مرة واحدة عند إرفاق المتتبِّع ومرة أخرى في كل مرة تتغيّر فيها البيانات، بما في ذلك أي عُقد فرعية. يتم تمرير snapshot إلى معاودة الاتصال بالحدث، ويحتوي على جميع البيانات في هذا الموقع الجغرافي، بما في ذلك بيانات العُقد الفرعية. إذا لم تكن هناك بيانات، سيعرض اللقطة false عند استدعاء exists() وnil عند قراءة السمة value.
يوضّح المثال التالي تطبيقًا للتدوين على وسائل التواصل الاجتماعي يسترد تفاصيل مشاركة من قاعدة البيانات:
Swift
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
يتلقّى المستمع FIRDataSnapshot يحتوي على البيانات في الموقع المحدّد في قاعدة البيانات في وقت الحدث في السمة value. يمكنك تعيين القيم للنوع الأصلي المناسب، مثل NSDictionary.
إذا لم تكن هناك بيانات في الموقع الجغرافي، تكون value هي nil.
قراءة البيانات مرة واحدة
القراءة مرة واحدة باستخدام getData()
تم تصميم حزمة تطوير البرامج (SDK) لإدارة التفاعلات مع خوادم قاعدة البيانات سواء كان تطبيقك متصلاً بالإنترنت أو غير متصل.
بشكل عام، عليك استخدام تقنيات أحداث القيمة الموضّحة أعلاه لقراءة البيانات من أجل تلقّي إشعارات بالتعديلات التي تطرأ على البيانات من الخادم الخلفي. تُقلّل هذه التقنية من استخدامك وفواتيرك، وتم تحسينها لمنح المستخدمين أفضل تجربة أثناء الاتصال بالإنترنت وعدم الاتصال به.
إذا كنت بحاجة إلى البيانات مرة واحدة فقط، يمكنك استخدام getData() للحصول على لقطة من البيانات من قاعدة البيانات. إذا تعذّر على getData() عرض قيمة الخادم لأي سبب، سيتحقق العميل من ذاكرة التخزين المؤقت المحلية ويعرض خطأ إذا لم يتم العثور على القيمة بعد.
يوضّح المثال التالي استرداد اسم مستخدم متاح للجميع مرة واحدة من قاعدة البيانات:
Swift
do { let snapshot = try await ref.child("users/\(uid)/username").getData() let userName = snapshot.value as? String ?? "Unknown" } catch { print(error) }
Objective-C
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid]; [[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) { if (error) { NSLog(@"Received an error %@", error); return; } NSString *userName = snapshot.value; }];
يمكن أن يؤدي الاستخدام غير الضروري لـ getData() إلى زيادة استخدام معدل نقل البيانات وانخفاض الأداء، ويمكن منع ذلك باستخدام متتبِّع في الوقت الفعلي كما هو موضّح أعلاه.
قراءة البيانات مرة واحدة باستخدام مراقب
في بعض الحالات، قد تريد عرض القيمة من ذاكرة التخزين المؤقت المحلية على الفور، بدلاً من التحقق من وجود قيمة معدّلة على الخادم. في هذه الحالات، يمكنك استخدام observeSingleEventOfType للحصول على البيانات من ذاكرة التخزين المؤقت المحلية على القرص على الفور.
يكون ذلك مفيدًا للبيانات التي يجب تحميلها مرة واحدة فقط ومن غير المتوقّع أن تتغيّر بشكل متكرّر أو تتطلّب الاستماع النشط. على سبيل المثال، يستخدم تطبيق التدوين في الأمثلة السابقة هذه الطريقة لتحميل الملف الشخصي للمستخدم عندما يبدأ في إنشاء مشاركة جديدة:
Swift
let userID = Auth.auth().currentUser?.uid ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in // Get user value let value = snapshot.value as? NSDictionary let username = value?["username"] as? String ?? "" let user = User(username: username) // ... }) { error in print(error.localizedDescription) }
Objective-C
NSString *userID = [FIRAuth auth].currentUser.uid; [[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { // Get user value User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]]; // ... } withCancelBlock:^(NSError * _Nonnull error) { NSLog(@"%@", error.localizedDescription); }];
تعديل البيانات أو حذفها
تعديل حقول معيّنة
للكتابة في عُقد فرعية معيّنة لعُقدة في الوقت نفسه بدون الكتابة فوق العُقد الفرعية الأخرى، استخدِم طريقة updateChildValues.
عند استدعاء updateChildValues، يمكنك تعديل قيم العُقد الفرعية ذات المستوى الأدنى من خلال تحديد مسار للمفتاح. إذا تم تخزين البيانات في مواقع جغرافية متعددة لتحسين إمكانية التوسّع
، يمكنك تعديل جميع مثيلات هذه البيانات باستخدام
توزيع موسَّع للبيانات. على سبيل المثال، قد يرغب تطبيق التدوين على وسائل التواصل الاجتماعي في إنشاء مشاركة وتعديلها في الوقت نفسه في خلاصة الأنشطة الحديثة وخلاصة نشاط المستخدم الذي ينشر المشاركة. لإجراء ذلك، يستخدم تطبيق التدوين رمزًا مثل هذا:
Swift
guard let key = ref.child("posts").childByAutoId().key else { return } let post = ["uid": userID, "author": username, "title": title, "body": body] let childUpdates = ["/posts/\(key)": post, "/user-posts/\(userID)/\(key)/": post] ref.updateChildValues(childUpdates)
Objective-C
NSString *key = [[_ref child:@"posts"] childByAutoId].key; NSDictionary *post = @{@"uid": userID, @"author": username, @"title": title, @"body": body}; NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post, [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post}; [_ref updateChildValues:childUpdates];
يستخدم هذا المثال childByAutoId لإنشاء مشاركة في العُقدة التي تحتوي على مشاركات لجميع المستخدمين في /posts/$postid واسترداد المفتاح في الوقت نفسه باستخدام getKey(). يمكن بعد ذلك استخدام المفتاح لإنشاء إدخال ثانٍ في مشاركات المستخدم في /user-posts/$userid/$postid.
باستخدام هذه المسارات، يمكنك إجراء تعديلات متزامنة على مواقع جغرافية متعددة في شجرة JSON من خلال استدعاء updateChildValues مرة واحدة، مثل كيفية إنشاء هذا المثال للمشاركة الجديدة في كلا الموقعَين الجغرافية. تُعدّ التعديلات المتزامنة التي يتم إجراؤها بهذه الطريقة كلّية: إما أن تنجح جميع التعديلات أو تفشل جميعها.
إضافة معاودة اتصال عند الإكمال
إذا أردت معرفة متى تم تثبيت بياناتك، يمكنك إضافة معاودة اتصال عند الإكمال. تأخذ كل من setValue وupdateChildValues معاودة اتصال اختيارية عند الإكمال يتم استدعاؤها عند تثبيت عملية الكتابة في قاعدة البيانات. يمكن أن يكون هذا المستمع مفيدًا لتتبُّع البيانات التي تم حفظها والبيانات التي لا تزال تتم مزامنتها. إذا لم ينجح الاستدعاء، يتم تمرير كائن خطأ إلى المتتبِّع يشير إلى سبب حدوث الخطأ.
Swift
do { try await ref.child("users").child(user.uid).setValue(["username": username]) print("Data saved successfully!") } catch { print("Data could not be saved: \(error).") }
Objective-C
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { if (error) { NSLog(@"Data could not be saved: %@", error); } else { NSLog(@"Data saved successfully."); } }];
حذف البيانات
أسهل طريقة لحذف البيانات هي استدعاء removeValue على مرجع لموقع هذه البيانات.
يمكنك أيضًا الحذف من خلال تحديد nil كقيمة لعملية كتابة أخرى، مثل setValue أو updateChildValues. يمكنك استخدام هذه التقنية مع updateChildValues لحذف عُقد فرعية متعددة في طلب بيانات من واجهة برمجة التطبيقات واحد.
إلغاء إرفاق المستمعين
لا تتوقف المراقبات تلقائيًا عن مزامنة البيانات عند مغادرة ViewController. إذا لم تتم إزالة مراقب بشكل صحيح، سيستمر في مزامنة البيانات مع الذاكرة المحلية. عندما لا يعود هناك حاجة إلى مراقب، يمكنك إزالته من خلال تمرير FIRDatabaseHandle المرتبط إلى طريقة removeObserverWithHandle.
عند إضافة معاودة اتصال إلى مرجع، يتم عرض FIRDatabaseHandle.
يمكن استخدام هذه المقابض لإزالة معاودة الاتصال.
إذا تمت إضافة مستمعين متعددين إلى مرجع قاعدة بيانات، يتم استدعاء كل مستمع عند ظهور حدث. لإيقاف مزامنة البيانات في هذا الموقع الجغرافي، عليك إزالة جميع المراقبات في الموقع الجغرافي من خلال استدعاء طريقة removeAllObservers.
لا يؤدي استدعاء removeObserverWithHandle أو removeAllObservers على مستمع إلى إزالة المستمعين المسجّلين على العُقد الفرعية تلقائيًا، بل عليك أيضًا تتبُّع هذه المراجع أو المقابض لإزالتها.
حفظ البيانات كمعاملات
عند العمل مع البيانات التي قد تتلف بسبب التعديلات المتزامنة ، مثل العدادات التزايدية، يمكنك استخدام عملية معاملة. تمنح هذه العملية وسيطتَين: دالة تعديل ومعاودة اتصال اختيارية عند الإكمال. تأخذ دالة التعديل الحالة الحالية للبيانات كمعلَمة وتعرض الحالة الجديدة المطلوبة التي تريد كتابتها.
على سبيل المثال، في تطبيق التدوين على وسائل التواصل الاجتماعي، يمكنك السماح للمستخدمين بوضع نجمة على المشاركات وإزالتها وتتبُّع عدد النجوم التي حصلت عليها المشاركة على النحو التالي:
Swift
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in if var post = currentData.value as? [String: AnyObject], let uid = Auth.auth().currentUser?.uid { var stars: [String: Bool] stars = post["stars"] as? [String: Bool] ?? [:] var starCount = post["starCount"] as? Int ?? 0 if let _ = stars[uid] { // Unstar the post and remove self from stars starCount -= 1 stars.removeValue(forKey: uid) } else { // Star the post and add self to stars starCount += 1 stars[uid] = true } post["starCount"] = starCount as AnyObject? post["stars"] = stars as AnyObject? // Set value and report transaction success currentData.value = post return TransactionResult.success(withValue: currentData) } return TransactionResult.success(withValue: currentData) }) { error, committed, snapshot in if let error = error { print(error.localizedDescription) } }
Objective-C
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) { NSMutableDictionary *post = currentData.value; if (!post || [post isEqual:[NSNull null]]) { return [FIRTransactionResult successWithValue:currentData]; } NSMutableDictionary *stars = post[@"stars"]; if (!stars) { stars = [[NSMutableDictionary alloc] initWithCapacity:1]; } NSString *uid = [FIRAuth auth].currentUser.uid; int starCount = [post[@"starCount"] intValue]; if (stars[uid]) { // Unstar the post and remove self from stars starCount--; [stars removeObjectForKey:uid]; } else { // Star the post and add self to stars starCount++; stars[uid] = @YES; } post[@"stars"] = stars; post[@"starCount"] = @(starCount); // Set value and report transaction success currentData.value = post; return [FIRTransactionResult successWithValue:currentData]; } andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) { // Transaction completed if (error) { NSLog(@"%@", error.localizedDescription); } }];
يمنع استخدام المعاملة أن تكون أعداد النجوم غير صحيحة إذا وضع عدة مستخدمين نجمة على المشاركة نفسها في الوقت نفسه أو إذا كانت بيانات العميل قديمة. تكون القيمة الواردة في الفئة FIRMutableData في البداية هي آخر قيمة معروفة للعميل للمسار، أو nil إذا لم تكن هناك قيمة. يقارن الخادم القيمة الأولية بالقيمة الحالية ويقبل المعاملة إذا كانت القيم متطابقة، أو يرفضها. إذا تم رفض المعاملة، يعرض الخادم القيمة الحالية للعميل، الذي يُشغّل المعاملة مرة أخرى بالقيمة المعدّلة. يتكرّر ذلك إلى أن يتم قبول المعاملة أو يتم إجراء عدد كبير جدًا من المحاولات.
الزيادات الكلية من جهة الخادم
في حالة الاستخدام أعلاه، نكتب قيمتَين في قاعدة البيانات: رقم تعريف المستخدم الذي يضع نجمة على المشاركة أو يزيلها، وعدد النجوم المتزايد. إذا كنا نعرف مسبقًا أنّ المستخدم يضع نجمة على المشاركة، يمكننا استخدام عملية زيادة كلية بدلاً من المعاملة.
Swift
let updates = [ "posts/\(postID)/stars/\(userID)": true, "posts/\(postID)/starCount": ServerValue.increment(1), "user-posts/\(postID)/stars/\(userID)": true, "user-posts/\(postID)/starCount": ServerValue.increment(1) ] as [String : Any] Database.database().reference().updateChildValues(updates)
Objective-C
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1], [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]}; [[[FIRDatabase database] reference] updateChildValues:updates];
لا يستخدم هذا الرمز عملية معاملة، لذا لا تتم إعادة تشغيله تلقائيًا إذا كان هناك تعديل متعارض. ومع ذلك، بما أنّ عملية الزيادة تحدث مباشرةً على خادم قاعدة البيانات، ليس هناك أي احتمال لحدوث تعارض.
إذا أردت رصد التعارضات الخاصة بالتطبيق ورفضها، مثل وضع مستخدم نجمة على مشاركة سبق أن وضع نجمة عليها، عليك كتابة قواعد أمان مخصّصة لحالة الاستخدام هذه.
العمل مع البيانات بلا اتصال بالإنترنت
إذا فقد العميل اتصاله بالشبكة، سيستمر تطبيقك في العمل بشكل صحيح.
يحتفظ كل عميل متصل بقاعدة بيانات Firebase بنسخته الداخلية الخاصة من أي بيانات نشطة. عند كتابة البيانات، تتم كتابتها في هذه النسخة المحلية أولاً. بعد ذلك، يزامن عميل Firebase هذه البيانات مع خوادم قاعدة البيانات البعيدة ومع العملاء الآخرين على أساس "بذل أفضل جهد".
نتيجةً لذلك، تؤدي جميع عمليات الكتابة في قاعدة البيانات إلى تفعيل الأحداث المحلية على الفور، قبل كتابة أي بيانات على الخادم. يعني ذلك أنّ تطبيقك يظل سريع الاستجابة بغض النظر عن وقت استجابة الشبكة أو الاتصال.
بمجرد استعادة الاتصال، يتلقّى تطبيقك المجموعة المناسبة من الأحداث حتى تتم مزامنة العميل مع حالة الخادم الحالية، بدون الحاجة إلى كتابة أي رمز مخصّص.
سنتحدّث أكثر عن السلوك بلا اتصال بالإنترنت في مزيد من المعلومات عن الإمكانات على الإنترنت وبلا اتصال بالإنترنت.