(اختياري) إنشاء نموذج أوّلي واختباره باستخدام 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 المتاحة على النحو التالي:
NSString
NSNumber
NSDictionary
NSArray
على سبيل المثال، يمكنك إضافة مستخدم لديه 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
كتلة اكتمال اختيارية يتمّ استدعاؤها عند تأكيد عملية الكتابة في قاعدة بيانات MySQL. يمكن أن يكون هذا المستمع مفيدًا لتتبُّع البيانات التي تم
حفظها والبيانات التي لا تزال تتم مزامنتها. إذا لم تنجح المكالمة،
يتم تمرير كائن خطأ إلى المستمع يشير إلى سبب حدوث الخطأ.
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
. في حال عدم إزالة مراقب بشكل صحيح، سيستمر في مزامنة data مع الذاكرة المحلية. عندما لا تعود هناك حاجة إلى أحد المراقبين، يمكنك إزالته من خلال تمرير FIRDatabaseHandle
المرتبط إلى طريقة removeObserverWithHandle
.
عند إضافة كتلة طلب معاودة الاتصال إلى مرجع، يتم عرض FIRDatabaseHandle
.
يمكن استخدام هذه الأسماء المعرِّفة لإزالة القسم المخصّص لطلب معاودة الاتصال.
في حال إضافة مستمعين متعدّدين إلى مرجع قاعدة بيانات، يتم
استدعاء كل مستمع عند حدوث حدث. لإيقاف مزامنة البيانات في هذا الموقع الجغرافي،
يجب إزالة جميع المراقبين في موقع جغرافي من خلال استدعاء الأسلوب removeAllObservers
.
لا يؤدي استدعاء removeObserverWithHandle
أو removeAllObservers
على مستمع
إلى إزالة المستمعين المسجّلين تلقائيًا في عقده الفرعية، بل يجب أيضًا
تتبُّع هذه الإشارات أو الأسماء المعرِّفة لإزالتها.
حفظ البيانات كمعاملات
عند العمل مع بيانات يمكن أن تتلف بسبب تعديلات مماثلة، مثل العدّادات المتزايدة، يمكنك استخدام عملية معاملة. يمكنك منح هذه العملية وسيطتين: دالة تعديل ودالّة ردّ اتصال اختيارية للإكمال. تأخذ دالة update الحالة الحالية للبيانات كأحد المَعلمات وتُرجع الحالة الجديدة المطلوبة التي تريد كتابتها.
على سبيل المثال، في مثال تطبيق التدوين الاجتماعي، يمكنك السماح للمستخدمين بتسجيل المحتوى المميّز وعدم تسجيله وتتبُّع عدد النجوم التي حصل عليها المحتوى، وذلك على النحو التالي:
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 على مزامنة تلك البيانات مع خوادم قواعد البيانات البعيدة ومع العملاء الآخرين على أساس "أفضل جهد".
ونتيجةً لذلك، تؤدي جميع عمليات الكتابة إلى قاعدة البيانات إلى بدء الأحداث المحلية على الفور، قبل كتابة أي بيانات على الخادم. وهذا يعني أنّ تطبيقك يظلّ سريع الاستجابة بغض النظر عن وقت استجابة الشبكة أو الاتصال بها.
بعد إعادة الاتصال، يتلقّى تطبيقك المجموعة المناسبة من الأحداث حتى تتم مزامنة العميل مع حالة الخادم الحالية، بدون الحاجة إلى كتابة أي رمز مخصّص.
سنتحدث أكثر عن السلوك بلا إنترنت في مقالة مزيد من المعلومات حول الإمكانات المتاحة على الإنترنت وبلا إنترنت.