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

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

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

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

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

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

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

احصل على FIRdatabaseReference

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

سويفت

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

ref = Database.database().reference()

ج موضوعية

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

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

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

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

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

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

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

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

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

سويفت

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

ج موضوعية

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

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

سويفت

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

ج موضوعية

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

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

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

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

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

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

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

سويفت

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

ج موضوعية

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

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

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

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

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

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

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

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

سويفت

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

ج موضوعية

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

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

سويفت

ملاحظة: منتج 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)
}

ج موضوعية

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

سويفت

ملاحظة: منتج 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)

ج موضوعية

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

ج موضوعية

ملاحظة: منتج 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 ​​لحذف عدة توابع في استدعاء واحد لواجهة برمجة التطبيقات (API).

افصل المستمعين

لا يتوقف المراقبون عن مزامنة البيانات تلقائيًا عند مغادرة ViewController . إذا لم تتم إزالة المراقب بشكل صحيح، فسيستمر في مزامنة البيانات مع الذاكرة المحلية. عندما لا تكون هناك حاجة إلى مراقب، قم بإزالته عن طريق تمرير FIRDatabaseHandle المرتبط إلى الأسلوب removeObserverWithHandle .

عند إضافة كتلة رد اتصال إلى مرجع، يتم إرجاع FIRDatabaseHandle . يمكن استخدام هذه المقابض لإزالة كتلة رد الاتصال.

إذا تمت إضافة عدة مستمعين إلى مرجع قاعدة بيانات، فسيتم استدعاء كل مستمع عند رفع حدث ما. لإيقاف مزامنة البيانات في ذلك الموقع، يجب عليك إزالة جميع المراقبين في الموقع عن طريق استدعاء الأسلوب removeAllObservers .

لا يؤدي استدعاء removeObserverWithHandle أو removeAllObservers على المستمع إلى إزالة المستمعين المسجلين في العقد الفرعية الخاصة به تلقائيًا؛ يجب عليك أيضًا تتبع تلك المراجع أو المقابض لإزالتها.

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

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

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

سويفت

ملاحظة: منتج 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)
  }
}

ج موضوعية

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

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

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

سويفت

ملاحظة: منتج 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)

ج موضوعية

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

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

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

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

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