יכולות לא מקוונות בפלטפורמות של אפל

יישומי Firebase פועלים גם אם האפליקציה שלך מאבדת זמנית את חיבור הרשת שלה. בנוסף, Firebase מספקת כלים לשמירה על נתונים מקומיים, ניהול נוכחות וטיפול בהשהייה.

התמדה בדיסק

אפליקציות Firebase מטפלות באופן אוטומטי בהפרעות רשת זמניות. נתונים מאוחסנים במטמון זמינים במצב לא מקוון ו-Firebase שולח מחדש כל כתיבה כאשר קישוריות הרשת משוחזרת.

כאשר אתה מפעיל התמדה בדיסק, האפליקציה שלך כותבת את הנתונים באופן מקומי למכשיר כך שהאפליקציה שלך תוכל לשמור על מצב במצב לא מקוון, גם אם המשתמש או מערכת ההפעלה מאתחלים את האפליקציה.

אתה יכול לאפשר התמדה בדיסק עם שורת קוד אחת בלבד.

מָהִיר

הערה: מוצר 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 מסנכרן ומאחסן עותק מקומי של הנתונים עבור מאזינים פעילים. בנוסף, תוכל לשמור מיקומים ספציפיים מסונכרנים.

מָהִיר

הערה: מוצר 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 מוריד אוטומטית את הנתונים במיקומים אלה ושומר אותם מסונכרנים גם אם אין להפניה מאזינים פעילים. אתה יכול לבטל את הסנכרון בחזרה עם שורת הקוד הבאה.

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
scoresRef.keepSynced(false)

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
[scoresRef keepSynced:NO];

כברירת מחדל, 10MB של נתונים שסונכרנו בעבר נשמרים במטמון. זה אמור להספיק לרוב היישומים. אם המטמון עולה על גודלו המוגדר, מסד הנתונים של Firebase Realtime מנקה נתונים שנעשה בהם שימוש לפחות לאחרונה. נתונים שנשמרים מסונכרנים אינם נמחקים מהמטמון.

שאילתת נתונים במצב לא מקוון

מסד הנתונים בזמן אמת של Firebase מאחסן נתונים שהוחזרו משאילתה לשימוש במצב לא מקוון. עבור שאילתות שנבנו במצב לא מקוון, מסד הנתונים בזמן אמת של Firebase ממשיך לעבוד עבור נתונים שנטענו בעבר. אם הנתונים המבוקשים לא נטענו, מסד הנתונים בזמן אמת של Firebase טוען נתונים מהמטמון המקומי. כאשר קישוריות רשת זמינה שוב, הנתונים נטענים וישקפו את השאילתה.

לדוגמה, קוד זה מבצע שאילתות עבור ארבעת הפריטים האחרונים במסד נתונים בזמן אמת של Firebase של ציונים

מָהִיר

הערה: מוצר 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);
    }];

נניח שהמשתמש מאבד את החיבור, עובר למצב לא מקוון ומפעיל מחדש את האפליקציה. כשהאפליקציה עדיין במצב לא מקוון, האפליקציה מחפשת את שני הפריטים האחרונים מאותו מיקום. שאילתה זו תחזיר בהצלחה את שני הפריטים האחרונים מכיוון שהאפליקציה טענה את כל ארבעת הפריטים בשאילתה למעלה.

מָהִיר

הערה: מוצר 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 מספקים פרימיטיבים פשוטים שבהם אתה יכול להשתמש כדי לכתוב למסד הנתונים כאשר לקוח מתנתק משרתי Firebase Database. עדכונים אלו מתרחשים בין אם הלקוח מתנתק בצורה נקייה או לא, כך שאתה יכול לסמוך עליהם כדי לנקות נתונים גם אם חיבור נותק או לקוח קורס. כל פעולות הכתיבה, כולל הגדרה, עדכון והסרה, יכולות להתבצע עם ניתוק.

הנה דוגמה פשוטה לכתיבת נתונים בעת ניתוק באמצעות הפרימיטיבי onDisconnect :

מָהִיר

הערה: מוצר 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 צורף כהלכה:

מָהִיר

הערה: מוצר 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() :

מָהִיר

הערה: מוצר 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 משתנה. הנה דוגמא:

מָהִיר

הערה: מוצר 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 כ-false, אין זה ערובה לכך שגם לקוח נפרד יקרא false.

טיפול ב-Latency

חותמות זמן של שרת

שרתי Firebase Realtime Database מספקים מנגנון להכנסת חותמות זמן שנוצרו בשרת כנתונים. תכונה זו, בשילוב עם onDisconnect , מספקת דרך קלה לרשום באופן מהימן את השעה שבה לקוח Realtime Database התנתק:

מָהִיר

הערה: מוצר 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 מוסיפים לזמן המדווח המקומי (זמן עידן באלפיות שניות) כדי להעריך את זמן השרת. שימו לב שהדיוק של היסט זה יכול להיות מושפע מהשהייה ברשת, ולכן הוא שימושי בעיקר לגילוי אי התאמות גדולות (> שנייה אחת) בזמן השעון.

מָהִיר

הערה: מוצר 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 מקוון או לא. לקוחות מגדירים את המיקום הזה כאמת כשהם נכנסים לאינטרנט וחותמת זמן כשהם מתנתקים. חותמת זמן זו מציינת את הפעם האחרונה שהמשתמש הנתון היה מקוון.

שים לב שהאפליקציה שלך צריכה להעמיד בתור את פעולות הניתוק לפני שמשתמש מסומן באינטרנט, כדי למנוע תנאי מרוץ במקרה שהחיבור לרשת של הלקוח אבד לפני שניתן לשלוח את שתי הפקודות לשרת.

הנה מערכת נוכחות משתמש פשוטה:

מָהִיר

הערה: מוצר 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]];
  }
}];