قراءة وكتابة البيانات على منصات Apple

(اختياري) النموذج الأولي والاختبار باستخدام Firebase Local Emulator Suite

قبل التحدث عن كيفية قراءة تطبيقك من قاعدة بيانات Realtime والكتابة إليها ، دعنا نقدم مجموعة من الأدوات التي يمكنك استخدامها لتصميم واختبار وظيفة Realtime Database: Firebase Local Emulator Suite. إذا كنت تجرب نماذج بيانات مختلفة ، أو تقوم بتحسين قواعد الأمان الخاصة بك ، أو تعمل على إيجاد الطريقة الأكثر فعالية من حيث التكلفة للتفاعل مع النهاية الخلفية ، فإن القدرة على العمل محليًا دون نشر الخدمات المباشرة يمكن أن تكون فكرة رائعة.

يعد محاكي Realtime Database جزءًا من Local Emulator Suite ، والذي يمكّن تطبيقك من التفاعل مع محتوى قاعدة البيانات التي تمت محاكاتها وتكوينها ، بالإضافة إلى موارد المشروع التي تمت محاكاتها اختياريًا (الوظائف وقواعد البيانات الأخرى وقواعد الأمان).

يتضمن استخدام محاكي Realtime Database بضع خطوات فقط:

  1. إضافة سطر من التعليمات البرمجية إلى التكوين التجريبي لتطبيقك للاتصال بالمحاكي.
  2. من جذر دليل المشروع المحلي ، قم بتشغيل firebase emulators:start .
  3. إجراء مكالمات من رمز النموذج الأولي لتطبيقك باستخدام SDK للنظام الأساسي لقاعدة البيانات Realtime كالمعتاد ، أو باستخدام Realtime Database REST API.

تتوفر إرشادات تفصيلية تتضمن Realtime Database و Cloud Functions . يجب عليك أيضًا إلقاء نظرة على مقدمة Local Emulator Suite .

احصل على FIRDatabaseReference

لقراءة البيانات أو كتابتها من قاعدة البيانات ، تحتاج إلى مثيل FIRDatabaseReference :

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
var ref: DatabaseReference!

ref = Database.database().reference()

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

اكتب البيانات

يغطي هذا المستند أساسيات قراءة بيانات Firebase وكتابتها.

تتم كتابة بيانات Firebase في مرجع Database ويتم استردادها عن طريق إرفاق مستمع غير متزامن بالمرجع. يتم تشغيل المستمع مرة واحدة للحالة الأولية للبيانات ومرة ​​أخرى في أي وقت تتغير فيه البيانات.

عمليات الكتابة الأساسية

لعمليات الكتابة الأساسية ، يمكنك استخدام setValue لحفظ البيانات في مرجع محدد ، واستبدال أي بيانات موجودة في ذلك المسار. يمكنك استخدام هذه الطريقة من أجل:

  • أنواع المرور التي تتوافق مع أنواع JSON المتاحة على النحو التالي:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

على سبيل المثال ، يمكنك إضافة مستخدم باستخدام setValue على النحو التالي:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
self.ref.child("users").child(user.uid).setValue(["username": username])

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

يؤدي استخدام setValue بهذه الطريقة إلى الكتابة فوق البيانات الموجودة في الموقع المحدد ، بما في ذلك أي عقد فرعية. ومع ذلك ، لا يزال بإمكانك تحديث الطفل دون إعادة كتابة الكائن بأكمله. إذا كنت تريد السماح للمستخدمين بتحديث ملفاتهم الشخصية ، فيمكنك تحديث اسم المستخدم على النحو التالي:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
self.ref.child("users/\(user.uid)/username").setValue(username)

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

إقرأ البيانات

اقرأ البيانات من خلال الاستماع إلى الأحداث القيمة

لقراءة البيانات في مسار والاستماع إلى التغييرات ، استخدم observeEventType:withBlock of FIRDatabaseReference لمراقبة أحداث FIRDataEventTypeValue .

نوع الحدث استخدام نموذجي
FIRDataEventTypeValue اقرأ واستمع إلى التغييرات التي تم إجراؤها على محتويات المسار بالكامل.

يمكنك استخدام حدث FIRDataEventTypeValue لقراءة البيانات في مسار معين ، كما هو موجود في وقت الحدث. يتم تشغيل هذه الطريقة مرة واحدة عند إرفاق المستمع ومرة ​​أخرى في كل مرة تتغير البيانات ، بما في ذلك أي أطفال. يتم تمرير رد اتصال الحدث snapshot تحتوي على جميع البيانات الموجودة في ذلك الموقع ، بما في ذلك البيانات التابعة. إذا لم تكن هناك بيانات ، فستعيد اللقطة false عند استدعاء exists() nil عند قراءة خاصية value الخاصة بها.

يوضح المثال التالي تطبيق التدوين الاجتماعي الذي يسترجع تفاصيل منشور من قاعدة البيانات:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

يتلقى المستمع FIRDataSnapshot يحتوي على البيانات في الموقع المحدد في قاعدة البيانات في وقت الحدث في خاصية value الخاصة به. يمكنك تعيين القيم إلى النوع الأصلي المناسب ، مثل NSDictionary . في حالة عدم وجود بيانات في الموقع ، تكون value nil .

اقرأ البيانات مرة واحدة

اقرأ مرة واحدة باستخدام getData ()

تم تصميم SDK لإدارة التفاعلات مع خوادم قاعدة البيانات سواء كان تطبيقك متصلاً بالإنترنت أو غير متصل بالإنترنت.

بشكل عام ، يجب عليك استخدام تقنيات أحداث القيمة الموضحة أعلاه لقراءة البيانات للحصول على إشعار بتحديثات البيانات من الواجهة الخلفية. تقلل هذه التقنية من استخدامك وفواتيرك ، وقد تم تحسينها لمنح المستخدمين أفضل تجربة أثناء اتصالهم بالإنترنت وغير متصل.

إذا كنت بحاجة إلى البيانات مرة واحدة فقط ، يمكنك استخدام getData() للحصول على لقطة من البيانات من قاعدة البيانات. إذا لم تتمكن getData() لأي سبب من إرجاع قيمة الخادم ، فسيقوم العميل بفحص ذاكرة التخزين المؤقت المحلية وإرجاع خطأ إذا لم يتم العثور على القيمة.

يوضح المثال التالي استرداد اسم المستخدم الموجه للعموم للمستخدم مرة واحدة من قاعدة البيانات:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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 للحصول على البيانات من ذاكرة التخزين المؤقت على القرص المحلي على الفور.

هذا مفيد للبيانات التي يجب تحميلها مرة واحدة فقط ولا يُتوقع أن تتغير بشكل متكرر أو تتطلب الاستماع النشط. على سبيل المثال ، يستخدم تطبيق التدوين في الأمثلة السابقة هذه الطريقة لتحميل ملف تعريف المستخدم عند بدء تأليف منشور جديد:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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)
}

ج موضوعية

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

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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)

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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 ​​كتلة إكمال اختيارية يتم استدعاؤها عندما يتم الالتزام بالكتابة في قاعدة البيانات. يمكن أن يكون هذا المستمع مفيدًا لتتبع البيانات التي تم حفظها والبيانات التي لا تزال قيد المزامنة. إذا لم تنجح المكالمة ، يتم تمرير كائن خطأ للمستمع يشير إلى سبب حدوث الفشل.

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
[[[_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 على مستمع إلى إزالة المستمعين المسجلين في العقد التابعة له تلقائيًا ؛ يجب عليك أيضًا تتبع تلك المراجع أو المقابض لإزالتها.

حفظ البيانات في المعاملات

عند العمل مع البيانات التي يمكن أن تتلف بسبب التعديلات المتزامنة ، مثل العدادات المتزايدة ، يمكنك استخدام عملية معاملة . أنت تعطي هذه العملية وسيطين: وظيفة تحديث واستدعاء إكمال اختياري. تأخذ وظيفة التحديث الحالة الحالية للبيانات كوسيطة وتعيد الحالة الجديدة المرغوبة التي ترغب في كتابتها.

على سبيل المثال ، في مثال تطبيق التدوين الاجتماعي ، يمكنك السماح للمستخدمين بتمييز المنشورات بنجمة وإلغاء التمييز بنجمة وتتبع عدد النجوم التي تلقاها المنشور على النحو التالي:

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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)
  }
}

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
[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 إذا لم يكن هناك أي قيمة. يقارن الخادم القيمة الأولية بالقيمة الحالية ويقبل المعاملة إذا تطابق القيم أو يرفضها. إذا تم رفض المعاملة ، يقوم الخادم بإرجاع القيمة الحالية إلى العميل ، والذي يقوم بتشغيل المعاملة مرة أخرى بالقيمة المحدثة. يتكرر هذا حتى يتم قبول المعاملة أو إجراء العديد من المحاولات.

الزيادات الذرية من جانب الخادم

في حالة الاستخدام المذكورة أعلاه ، نكتب قيمتين إلى قاعدة البيانات: معرّف المستخدم الذي يميز / يزيل النجوم عن المشاركة ، وعدد النجوم المتزايد. إذا علمنا بالفعل أن المستخدم يقوم بتمثيل المنشور ، فيمكننا استخدام عملية الزيادة الذرية بدلاً من المعاملة.

سويفت

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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);

ج موضوعية

ملاحظة: منتج Firebase هذا غير متاح في هدف مقطع التطبيق.
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 بعد ذلك بمزامنة تلك البيانات مع خوادم قاعدة البيانات البعيدة ومع العملاء الآخرين على أساس "أفضل جهد".

نتيجة لذلك ، تقوم جميع عمليات الكتابة في قاعدة البيانات بتشغيل الأحداث المحلية على الفور ، قبل كتابة أي بيانات إلى الخادم. هذا يعني أن تطبيقك يظل مستجيبًا بغض النظر عن زمن انتقال الشبكة أو الاتصال.

بمجرد إعادة إنشاء الاتصال ، يتلقى تطبيقك مجموعة الأحداث المناسبة بحيث يتزامن العميل مع حالة الخادم الحالية ، دون الحاجة إلى كتابة أي رمز مخصص.

سنتحدث أكثر عن السلوك في وضع عدم الاتصال في معرفة المزيد حول إمكانات الاتصال بالإنترنت وغير المتصلة .

الخطوات التالية