Apple 平台上的離線功能

即使您的應用程式暫時失去網路連接,Firebase 應用程式也能正常運作。此外,Firebase 還提供了用於在本地保存資料、管理狀態和處理延遲的工具。

磁碟持久化

Firebase 應用程式會自動處理臨時網路中斷。快取資料在離線狀態下可用,並且當網路連線恢復時,Firebase 會重新傳送所有寫入。

當您啟用磁碟持久性時,您的應用程式會將資料本機寫入設備,以便您的應用程式可以在離線狀態下保持狀態,即使使用者或作業系統重新啟動應用程式也是如此。

您只需一行程式碼即可啟用磁碟持久性。

迅速

注意:此 Firebase 產品在 App Clip 目標上不可用。
Database.database().isPersistenceEnabled = true

Objective-C

注意:此 Firebase 產品在 App Clip 目標上不可用。
[FIRDatabase database].persistenceEnabled = YES;

持久行為

透過啟用持久性,Firebase 即時資料庫用戶端在線上時同步的任何資料都會持久保存到磁碟,並且即使在使用者或作業系統重新啟動應用程式時也可以離線使用。這意味著您的應用程式可以像線上一樣使用儲存在快取中的本機資料。偵聽器回調將繼續觸發本機更新。

Firebase 即時資料庫用戶端會自動保留應用離線時執行的所有寫入作業的佇列。啟用持久性後,此佇列也會持久保存到磁碟,因此當使用者或作業系統重新啟動應用程式時,所有寫入都可用。當應用程式重新獲得連線時,所有操作都會傳送到 Firebase 即時資料庫伺服器。

如果您的應用程式使用Firebase 驗證,Firebase 即時資料庫用戶端會在應用程式重新啟動時保留使用者的身份驗證令牌。如果您的應用程式離線時身份驗證令牌過期,用戶端將暫停寫入操作,直到您的應用程式重新對使用者進行身份驗證,否則寫入操作可能會因安全性規則而失敗。

保持數據新鮮

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 即時資料庫用戶端也會自動下載這些位置的資料並使其保持同步。您可以使用以下程式碼行重新關閉同步。

迅速

注意:此 Firebase 產品在 App Clip 目標上不可用。
scoresRef.keepSynced(false)

Objective-C

注意:此 Firebase 產品在 App Clip 目標上不可用。
[scoresRef keepSynced:NO];

預設情況下,會快取 10MB 的先前同步資料。這對於大多數應用來說應該足夠了。如果快取超出其配置的大小,Firebase 即時資料庫將清除最近最少使用的資料。保持同步的資料不會從快取中清除。

離線查詢數據

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 即時資料庫用戶端透過使用持久性快取為得分最高的兩隻恐龍引發「新增子項」事件。但它不會引發「值」事件,因為應用程式從未在線上時執行過該查詢。

如果應用程式在離線時請求最後六個項目,它將立即獲得四個快取項目的「子新增」事件。當裝置重新上線時,Firebase 即時資料庫用戶端與伺服器同步並取得應用程式的最後兩個「新增子項」和「值」事件。

離線處理交易

應用程式離線時執行的任何事務都會排隊。一旦應用程式重新獲得網路連接,事務就會發送到即時資料庫伺服器。

管理存在

在即時應用程式中,檢測客戶端何時連接和斷開連接通常很有用。例如,您可能希望在用戶端斷開連線時將使用者標記為「離線」。

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!")

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 即時資料庫伺服器上。伺服器檢查安全性以確保使用者可以執行請求的寫入事件,並通知您的應用程式(如果該事件無效)。然後伺服器監視連接。如果連線在任何時候逾時,或被即時資料庫用戶端主動關閉,伺服器會再次檢查安全性(以確保操作仍然有效),然後呼叫該事件。

您的應用程式可以在寫入操作上使用回調來確保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 即時資料庫在/.info/connected中提供了一個特殊位置,每次 Firebase 即時資料庫用戶端的連線狀態變更時,該位置都會更新。這是一個例子:

迅速

注意:此 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是一個布林值,在即時資料庫用戶端之間不同步,因為該值取決於客戶端的狀態。換句話說,如果一個客戶端將/.info/connected讀取為 false,則不能保證另一個客戶端也會讀取 false。

處理延遲

伺服器時間戳

Firebase 即時資料庫伺服器提供了一種將服務器上產生的時間戳記作為資料插入的機制。此功能與onDisconnect結合,提供了一種簡單的方法來可靠地記錄即時資料庫用戶端斷開連線的時間:

迅速

注意:此 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 即時資料庫伺服器的時鐘偏差。您可以將回呼附加到位置/.info/serverTimeOffset來取得 Firebase 即時資料庫用戶端新增至本機報告時間(紀元時間,以毫秒為單位)的值(以毫秒為單位)以估計伺服器時間。請注意,此偏移的準確性可能會受到網路延遲的影響,因此主要用於發現時鐘時間中的較大(> 1 秒)差異。

迅速

注意:此 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);
}];

狀態應用程式範例

透過將斷開連接操作與連線狀態監控和伺服器時間戳記結合,您可以建立使用者存在系統。在此系統中,每個使用者將資料儲存在資料庫位置以指示即時資料庫用戶端是否線上。客戶端在上線時將此位置設為 true,在斷開連接時設定時間戳記。此時間戳表示給定使用者上次在線的時間。

請注意,您的應用程式應在將使用者標記為線上之前對斷開連接操作進行排隊,以避免在兩個命令發送到伺服器之前用戶端網路連線遺失的情況下出現任何競爭條件。

這是一個簡單的使用者存在系統:

迅速

注意:此 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]];
  }
}];