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

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

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

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

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

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

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

الحصول على FIRDatabaseReference

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

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

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

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

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

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

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

قراءة البيانات

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

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

نوع الحدث معدّل الاستخدام
FIRDataEventTypeValue قراءة التغييرات التي تتم على محتوى المسار بالكامل والاستماع إليها

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

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

يتلقّى المستمع FIRDataSnapshot الذي يحتوي على البيانات على النطاق الموقع الجغرافي في قاعدة البيانات وقت الحدث في السمة value إِنْتَ تعيين القيم للنوع الأصلي المناسب، مثل NSDictionary. وإذا لم تتوفّر بيانات في الموقع الجغرافي، تكون قيمة value هي nil.

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

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

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

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

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
[[[_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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
[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

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
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

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

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

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

سنتحدث أكثر عن السلوك خارج الإنترنت في مزيد من المعلومات حول الإمكانات على الإنترنت وبلا إنترنت

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