تعمل تطبيقات 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 Authentication، يُحفظ رمز مصادقة المستخدم لدى العميل 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 أحداث "إضافة طفل" لدينانين يحقّقان أعلى النقاط، وذلك باستخدام ذاكرة التخزين المؤقت الثابتة. ولن يؤدي ذلك إلى بدء حدث "value"، لأنّ التطبيق لم ينفِّذ هذا الطلب مطلقًا عندما كان متصلاً بالإنترنت.
إذا طلب التطبيق العناصر الستة الأخيرة أثناء عدم الاتصال بالإنترنت، سيتلقّى أحداث "إضافة عنصر فرعي" للعناصر الأربعة المخزّنة مؤقتًا على الفور. عندما يعود الجهاز إلى الاتصال بالإنترنت، تتم مزامنة العميلFirebase Realtime Database مع الخادم ويتلقّى حدثَي "إضافة العنصر" و 'القيمة" النهائيَين للتطبيق.
معالجة المعاملات بلا إنترنت
يتم وضع أي معاملات يتم إجراؤها عندما يكون التطبيق بلا إنترنت في قائمة الانتظار. بعد استعادة التطبيق للاتصال بالشبكة، يتم إرسال المعاملات إلى خادم Realtime Database.
إدارة "التواجد في المنزل"
في التطبيقات المستندة إلى الوقت الفعلي، غالبًا ما يكون من المفيد رصد حالات اتصال العميل وانقطاع الاتصال. على سبيل المثال، قد تحتاج إلى وضع علامة "غير متصل بالإنترنت" على مستخدم عندما ينقطع اتصال جهازه.
توفّر عملاء "قاعدة بيانات Firebase" عناصر أساسية بسيطة يمكنك استخدامها للقيام بعمليات تسجيل في قاعدة البيانات عند انقطاع اتصال أحد العملاء بخوادم "قاعدة بيانات Firebase". تحدث هذه التعديلات سواء تم قطع اتصال العميل بشكل نظيف أم لا، لذا يمكنك الاعتماد عليها لتنظيف البيانات حتى في حال انقطاع الاتصال أو تعطُّل أحد العملاء. يمكن تنفيذ جميع عمليات الكتابة، بما في ذلك الإعداد والتعديل والإزالة، عند انقطاع الاتصال.
في ما يلي مثال بسيط على كتابة البيانات عند انقطاع الاتصال باستخدام العنصر الأساسي
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]];
Clock Skew
على الرغم من أنّ 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]]; } }];