אפליקציות Firebase פועלות גם אם האפליקציה מאבדת באופן זמני את החיבור לרשת. בנוסף, Firebase מספק כלים לשמירת נתונים באופן מקומי, לניהול נוכחות ולטיפול בזמן אחזור.
אחסון מתמיד (persistent disk)
אפליקציות 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];
כברירת מחדל, 10MB של נתונים שסונכרנו בעבר נשמרים במטמון. זה אמור להספיק לרוב האפליקציות. אם המטמון חורג מהגודל שהוגדר לו, המערכת מבצעת 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 יוצר אירועים מסוג 'child added' לשני הדינוזאורים עם הדירוג הגבוה ביותר, באמצעות המטמון הקבוע. אבל לא יופעל אירוע 'value', כי האפליקציה אף פעם לא הפעילה את השאילתה הזו בזמן שהיא הייתה מחוברת לאינטרנט.
אם האפליקציה תבקש את ששת הפריטים האחרונים במצב אופליין, היא תקבל אירועים מסוג 'הוספת צאצא' עבור ארבעת הפריטים שנשמרו במטמון באופן מיידי. כשהמכשיר חוזר לאינטרנט, הלקוח Firebase Realtime Database מסנכרן עם השרת ומקבל את שני האירועים האחרונים מסוג 'child added' ואת האירוע 'value' לאפליקציה.
טיפול בעסקאות אופליין
כל העסקאות שמבוצעות כשהאפליקציה במצב אופליין נכנסות לתור. כשהאפליקציה מתחברת מחדש לרשת, העסקאות נשלחות לשרת Realtime Database.
ניהול הנוכחות
באפליקציות בזמן אמת, לרוב כדאי לזהות מתי לקוחות מתחברים ומתי הם מתנתקים. לדוגמה, יכול להיות שתרצו לסמן משתמש כ 'לא מקוון' כשהלקוח שלו מתנתק.
לקוחות של Firebase Database מספקים פרימיטיבים פשוטים שאפשר להשתמש בהם כדי לכתוב במסד הנתונים כשלקוח מתנתק מהשרתים של Firebase Database. העדכונים האלה מתרחשים גם אם הלקוח מתנתק בצורה תקינה וגם אם לא, כך שאפשר להסתמך עליהם כדי לנקות נתונים גם אם החיבור התנתק או שהלקוח קרס. אפשר לבצע את כל פעולות הכתיבה, כולל הגדרה, עדכון והסרה, גם כשהמכשיר לא מחובר.
דוגמה פשוטה לכתיבה של נתונים לאחר ניתוק באמצעות הפרימיטיב 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, השרת בודק את האבטחה בפעם השנייה (כדי לוודא שהפעולה עדיין תקפה) ולאחר מכן מפעיל את האירוע.
האפליקציה יכולה להשתמש בקריאה החוזרת (callback) של פעולת הכתיבה כדי לוודא שה-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
כ-false, אין ערובה לכך שלקוח נפרד יקרא גם הוא את הערך כ-false.
זמן האחזור בטיפול
חותמות זמן של שרת
שרתי 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 מחובר או לא. לקוחות מגדירים את המיקום הזה כ-true כשהם מחוברים לאינטרנט, ומגדירים חותמת זמן כשהם מתנתקים. חותמת הזמן הזו מציינת את הפעם האחרונה שהמשתמש היה אונליין.
חשוב לזכור שהאפליקציה צריכה להוסיף את פעולות הניתוק לתור לפני שמשתמש מסומן אופליין, כדי למנוע תנאי מרוץ במקרה שחיבורי הרשת של הלקוח יתנתקו לפני שאפשר יהיה לשלוח את שתי הפקודות לשרת.
הנה מערכת פשוטה לזיהוי נוכחות משתמשים:
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]]; } }];