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

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

ثبات القرص

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

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

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

سويفت

ملاحظة: منتج Firebase هذا غير متوفر في هدف App Clip.
Database.database().isPersistenceEnabled = true

ج موضوعية

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

سلوك الثبات

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

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

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

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

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

سويفت

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

ج موضوعية

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

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

سويفت

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

ج موضوعية

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

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

الاستعلام عن البيانات دون اتصال

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

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

سويفت

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

ج موضوعية

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

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

سويفت

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

ج موضوعية

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

إدارة الحضور

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

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

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

سويفت

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

ج موضوعية

ملاحظة: منتج 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، فإن الخادم يتحقق من الأمان مرة ثانية (للتأكد من أن العملية لا تزال صالحة) ثم يستدعي الحدث.

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

سويفت

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

ج موضوعية

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

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

سويفت

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

ج موضوعية

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

الكشف عن حالة الاتصال

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

سويفت

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

ج موضوعية

ملاحظة: منتج 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 لأن القيمة تعتمد على حالة العميل. بمعنى آخر، إذا قرأ أحد العملاء /.info/connected كـ false، فهذا لا يضمن أن العميل المنفصل سيقرأ أيضًا false.

التعامل مع الكمون

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

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

سويفت

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

ج موضوعية

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

سويفت

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

ج موضوعية

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

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

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

سويفت

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

ج موضوعية

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