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

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

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

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

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

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

Swift

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

Objective-C

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

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

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

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

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

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

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

Swift

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

Objective-C

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

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

Swift

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

Objective-C

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

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

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

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

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

Swift

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

Objective-C

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

Objective-C

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

Objective-C

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

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

Swift

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

Objective-C

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

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

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

Swift

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

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا في استهداف تطبيق Clip.
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 هذا في استهداف تطبيق Clip.
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 هذا في استهداف تطبيق 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 هذا في استهداف تطبيق Clip.
// 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 هذا في استهداف تطبيق Clip.
// 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]];
  }
}];