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