(اختياري) إنشاء نموذج أوّلي واختباره باستخدام 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 كالمعتاد، أو باستخدام واجهة برمجة التطبيقات REST الخاصة بمنصة Realtime Database
يتوفّر شرح تفصيلي يتضمّن 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
كتلة إكمال اختيارية يتم استدعاؤها عند إكمال عملية الكتابة في قاعدة البيانات. يمكن أن يكون هذا المستمع مفيدًا لتتبُّع البيانات التي تم حفظها والبيانات التي لا تزال تتم مزامنتها. إذا لم تنجح المكالمة،
سيتم تمرير كائن خطأ إلى المستمع يشير إلى سبب حدوث الخطأ.
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 هذه البيانات مع خوادم قاعدة البيانات البعيدة ومع العملاء الآخرين على أساس "بذل أفضل جهد".
نتيجةً لذلك، تؤدي جميع عمليات الكتابة إلى قاعدة البيانات إلى تشغيل الأحداث المحلية على الفور، قبل كتابة أي بيانات على الخادم. وهذا يعني أنّ تطبيقك سيظل متجاوبًا بغض النظر عن وقت استجابة الشبكة أو الاتصال بها.
وبعد إعادة إنشاء الاتصال، يتلقّى تطبيقك مجموعة الأحداث المناسبة لكي تتم مزامنة العميل مع حالة الخادم الحالية، بدون الحاجة إلى كتابة أي رمز مخصّص.
سنتحدّث أكثر عن السلوك غير الإلكتروني في مقالة مزيد من المعلومات عن الإمكانات على الإنترنت وخارجه.