إمكانات وضع عدم الاتصال بالإنترنت على منصات Apple

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

الاحتفاظ بالبيانات على القرص

تتعامل تطبيقات Firebase تلقائيًا مع انقطاعات الاتصال المؤقتة بالشبكة. تتوفّر البيانات المخزّنة مؤقتًا عندما تكون غير متصل بالإنترنت، ويعيد Firebase إرسال أي عمليات كتابة عند استعادة الاتصال بالشبكة.

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

يمكنك تفعيل ميزة الاحتفاظ بالبيانات على القرص باستخدام سطر واحد من التعليمات البرمجية فقط.

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
Database.database().isPersistenceEnabled = true

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
[FIRDatabase database].persistenceEnabled = YES;

سلوك الاستمرارية

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

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

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

الحفاظ على حداثة البيانات

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

ينزِّل برنامج Firebase Realtime Database تلقائيًا البيانات من هذه المواقع الجغرافية ويحافظ على مزامنتها حتى إذا لم يكن المرجع يحتوي على مستمعين نشطين. يمكنك إيقاف المزامنة مرة أخرى باستخدام سطر التعليمات البرمجية التالي:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
scoresRef.keepSynced(false)

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
[scoresRef keepSynced:NO];

يتم تلقائيًا تخزين 10 ميغابايت من البيانات التي تمت مزامنتها سابقًا مؤقتًا. وهذا سيكون كافيًا لمعظم التطبيقات. إذا تجاوزت ذاكرة التخزين المؤقت حجمها المحدَّد، تُزيل Firebase Realtime Database البيانات التي تم استخدامها مؤخرًا. لا تتم إزالة البيانات التي تتم مزامنتها من ذاكرة التخزين المؤقت.

طلب البيانات بلا إنترنت

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

على سبيل المثال، يطلب هذا الرمز البحث عن آخر أربعة عناصر في Firebase Realtime Database من النتائج.

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

في المثال السابق، يعرض العميل Firebase Realtime Database الأحداث "التي أضافها ثانوي" إلى الديناصورَين الأعلى تقييمًا، وذلك باستخدام ذاكرة التخزين المؤقت المتبقية. ولن يؤدي ذلك إلى بدء حدث "value"، لأنّ التطبيق لم ينفِّذ هذا الطلب مطلقًا عندما كان متصلاً بالإنترنت.

إذا طلب التطبيق العناصر الستة الأخيرة بلا اتصال بالإنترنت، سيحصل على الفور على أحداث "إضافة تابعة" للعناصر الأربعة المخزّنة مؤقتًا. عندما يعود الجهاز إلى الاتصال بالإنترنت، تتم مزامنة العميلFirebase Realtime Database مع الخادم ويتلقّى حدثَي "إضافة العنصر" و 'القيمة" النهائيَين للتطبيق.

معالجة المعاملات بلا إنترنت

يتم وضع أي معاملات يتم إجراؤها عندما يكون التطبيق بلا إنترنت في قائمة الانتظار. بعد أن يستعيد التطبيق الاتصال بالشبكة، يتم إرسال المعاملات إلى خادم Realtime Database.

إدارة التواجد

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

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

في ما يلي مثال بسيط على كتابة البيانات عند انقطاع الاتصال باستخدام العنصر الأساسي onDisconnect:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

آلية عمل onDisconnect

عند إنشاء عملية onDisconnect()، يتم تخزين العملية على خادم Firebase Realtime Database. يتحقّق الخادم من الأمان للتأكّد من أنّه يمكن للمستخدم تنفيذ حدث الكتابة المطلوب، ويُعلم تطبيقك إذا كان غير صالح. بعد ذلك، يراقب الخادم عملية الاتصال. إذا انتهت مهلة الاتصال في أي وقت أو تم إغلاقه بشكل نشط من قِبل برنامج Realtime Database، يتحقّق الخادم من الأمان مرة ثانية (للتأكّد من أنّ العملية لا تزال صالحة) ثم يستدعي الحدث.

يمكن لتطبيقك استخدام دالة الاستدعاء في عملية الكتابة للتأكّد من إرفاق onDisconnect بشكل صحيح:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

يمكن أيضًا إلغاء حدث onDisconnect من خلال الاتصال بالرقم .cancel():

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

رصد حالة الاتصال

بالنسبة إلى العديد من الميزات المتعلّقة بحالة الاتصال بالإنترنت، من المفيد لتطبيقك معرفة ما إذا كان متصلاً بالإنترنت أم لا. يوفّر Firebase Realtime Database موقعًا جغرافيًا خاصًا في /.info/connected يتم تعديله في كل مرة تتغير فيها حالة اتصال عميل Firebase Realtime Database. في ما يلي مثال:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected هي قيمة منطقية لا تتم مزامنة قيمتها بين عملاء Realtime Database لأنّ القيمة تعتمد على حالة العميل. بعبارة أخرى، إذا كان أحد العملاء يقرأ /.info/connected على أنّه خطأ، لا يضمن ذلك أن يقرأ عميل منفصل أيضًا خطأ.

وقت استجابة معالجة الطلبات

الطوابع الزمنية للخادم

توفّر خوادم Firebase Realtime Database آلية لإدراج علامات زمنية يتم إنشاؤها على الخادم كبيانات. توفّر هذه الميزة، إلى جانب onDisconnect، طريقة سهلة لتسجيل الوقت الذي انقطع فيه اتصال عميل Realtime Database بشكل موثوق:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

Clock Skew

على الرغم من أنّ firebase.database.ServerValue.TIMESTAMP أكثر دقة، ويُفضّل استخدامه في معظم عمليات القراءة/الكتابة، قد يكون من المفيد أحيانًا تقدير انحراف ساعة العميل مقارنةً بخوادم Firebase Realtime Database. يمكنك إرفاق طلب إعادة اتصال بالموقع /.info/serverTimeOffset للحصول على القيمة، بالمللي ثانية، التي يضيفها عملاء Firebase Realtime Database إلى الوقت المسجَّل محليًا (وقت البدء بالمللي ثانية) لتقدير وقت الخادم. يُرجى العِلم أنّ دقة هذا التباين قد تتأثر بوقت استجابة الشبكة، وبالتالي فهي مفيدة في المقام الأول لرصد تناقضات كبيرة (أكبر من ثانية واحدة) في وقت الساعة.

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في هدف App Clip.
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

نموذج تطبيق "التواجد"

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

يُرجى العِلم أنّ تطبيقك يجب أن يضع عمليات قطع الاتصال في "قائمة الانتظار" قبل وضع علامة "متصل" على أحد المستخدمين، وذلك لتجنُّب أي حالات تداخل في حال انقطاع اتصال العميل بالشبكة قبل أن يتم إرسال كلا الأمرَين إلى الخادم.

في ما يلي نظام بسيط لرصد تواجد المستخدمين:

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard snapshot.value as? Bool ?? false else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف "المقاطع الترويجية للتطبيقات".
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];


    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];