تعمل تطبيقات Firebase حتى إذا فقد تطبيقك الاتصال بالشبكة مؤقتًا بالإضافة إلى ذلك، توفّر Firebase أدوات للاحتفاظ بالبيانات محليًا، إدارة حالة الاتصال بالإنترنت والتعامل مع وقت الاستجابة.
الاحتفاظ بالبيانات على القرص
تتعامل تطبيقات Firebase تلقائيًا مع الانقطاعات المؤقتة في الشبكة. تتوفّر البيانات المخزّنة مؤقتًا أثناء عدم الاتصال بالإنترنت، وتعيد Firebase إرسال أي عمليات كتابة عند استعادة الاتصال بالشبكة.
عند تفعيل ميزة الاحتفاظ بالبيانات على القرص، يكتب تطبيقك البيانات محليًا على الـ جهاز، ما يتيح له الاحتفاظ بالحالة أثناء عدم الاتصال بالإنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق.
يمكنك تفعيل ميزة الاحتفاظ بالبيانات على القرص باستخدام سطر واحد فقط من التعليمات البرمجية.
Swift
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
سلوك الاحتفاظ بالبيانات
عند تفعيل ميزة الاحتفاظ بالبيانات، يتم الاحتفاظ على القرص بأي بيانات كان العميل Firebase Realtime Database سيُزامنها أثناء الاتصال بالإنترنت، وتصبح متاحة بلا إنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق. ويعني ذلك أنّ تطبيقك يعمل كما لو كان متصلاً بالإنترنت باستخدام البيانات المحلية المخزّنة في ذاكرة التخزين المؤقت. ستستمر عمليات معاودة الاتصال بالمستمعين في إطلاق التعديلات المحلية.
يحتفظ عميل Firebase Realtime Database تلقائيًا بقائمة انتظار لجميع عمليات الكتابة التي يتم إجراؤها أثناء عدم اتصال تطبيقك بالإنترنت. عند تفعيل ميزة الاحتفاظ بالبيانات، يتم أيضًا الاحتفاظ بقائمة الانتظار هذه على القرص، ما يتيح لك الوصول إلى جميع عمليات الكتابة عند إعادة تشغيل التطبيق من قِبل المستخدم أو نظام التشغيل. وعند استعادة إمكانية اتصال التطبيق بالإنترنت، يتم إرسال جميع العمليات إلى الخادم Firebase Realtime Database.
إذا كان تطبيقك يستخدم مصادقة Firebase، يحتفظ العميل Firebase Realtime Database برمز مصادقة المستخدم عند إعادة تشغيل التطبيق. إذا انتهت صلاحية رمز المصادقة أثناء عدم اتصال تطبيقك بالإنترنت، يوقف العميل عمليات الكتابة إلى أن يعيد تطبيقك مصادقة المستخدم، وإلا قد تفشل عمليات الكتابة بسبب قواعد الأمان.
الحفاظ على تحديث البيانات
تُزامن Firebase Realtime Database نسخة محلية من البيانات للمستمعين النشطين وتخزّنها. بالإضافة إلى ذلك، يمكنك الحفاظ على مزامنة مواقع معيّنة.
Swift
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
ينزّل العميل Firebase Realtime Database تلقائيًا البيانات في هذه المواقع ويحافظ على مزامنتها حتى إذا لم يكن للمرجع أي مستمعين نشطين. يمكنك إيقاف المزامنة مرة أخرى باستخدام سطر التعليمات البرمجية التالي.
Swift
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
يتم تلقائيًا تخزين 10 ميغابايت من البيانات التي تمت مزامنتها سابقًا في ذاكرة التخزين المؤقت. ويجب أن يكون هذا كافيًا لمعظم التطبيقات. إذا تجاوزت ذاكرة التخزين المؤقت الحجم الذي تم ضبطه، تحذف Firebase Realtime Database البيانات التي تم استخدامها الأقل مؤخرًا. لا يتم حذف البيانات التي يتم الحفاظ على مزامنتها من ذاكرة التخزين المؤقت.
طلب البيانات بلا إنترنت
يخزّن Firebase Realtime Database البيانات التي يتم عرضها من طلب بحث لاستخدامها بلا إنترنت. بالنسبة إلى طلبات البحث التي يتم إنشاؤها أثناء عدم الاتصال بالإنترنت، تواصل Firebase Realtime Database العمل مع البيانات التي تم تحميلها سابقًا. إذا لم يتم تحميل البيانات المطلوبة، يحمّل Firebase Realtime Database البيانات من ذاكرة التخزين المؤقت المحلية. عندما يصبح الاتصال بالشبكة متاحًا مرة أخرى، يتم تحميل البيانات وتعرض طلب البحث.
على سبيل المثال، يطلب هذا الرمز آخر أربعة عناصر في Firebase Realtime Database للنتائج.
Swift
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
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
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")") }
Objective-C
[[[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
let presenceRef = Database.database().reference(withPath: "disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnectSetValue("I disconnected!")
Objective-C
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
presenceRef.onDisconnectRemoveValue { error, reference in
if let error = error {
print("Could not establish onDisconnect event: \(error)")
}
}Objective-C
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) { if (error != nil) { NSLog(@"Could not establish onDisconnect event: %@", error); } }];
يمكن أيضًا إلغاء حدث onDisconnect من خلال استدعاء .cancel():
Swift
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()Objective-C
[presenceRef onDisconnectSetValue:@"I disconnected"]; // some time later when we change our minds [presenceRef cancelDisconnectOperations];
رصد حالة الاتصال
بالنسبة إلى العديد من الميزات المرتبطة بحالة الاتصال بالإنترنت، من المفيد أن يعرف تطبيقك
ما إذا كان متصلاً بالإنترنت أو لا. Firebase Realtime Database
توفّر موقعًا خاصًا على /.info/connected يتم تعديله في كل مرة تتغيّر فيها حالة اتصال عميل Firebase Realtime Database. في ما يلي مثال:
Swift
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
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
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline") userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
Objective-C
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
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
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
// 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
// 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]]; } }];