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

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

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

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

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا على هدف App Clip.
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 هذا على هدف App Clip.
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

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

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

Swift

ملاحظة: لا يتوفّر منتج Firebase هذا على هدف App Clip.
scoresRef.keepSynced(false)

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا على هدف App Clip.
[scoresRef keepSynced:NO];

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

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

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

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

Swift

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

Objective-C

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

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

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

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

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

إدارة حالة الاتصال بالإنترنت

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

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

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

Swift

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

Objective-C

ملاحظة: لا يتوفّر منتج Firebase هذا على هدف App 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 هذا على هدف App Clip.
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 هذا على هدف App Clip.
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

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

Objective-C

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

اختلاف التوقيت

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

Swift

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