Apple 平台上的離線功能

即使應用程式暫時失去網路連線,Firebase 應用程式仍可運作。此外,Firebase 還提供工具,可用於在本機保留資料、管理上線狀態及處理延遲。

磁碟持續性

Firebase 應用程式會自動處理暫時性的網路中斷。 離線時可使用快取資料,網路連線恢復後,Firebase 會重新傳送所有寫入作業。

啟用磁碟持續性後,應用程式會將資料寫入裝置本機,因此即使使用者或作業系統重新啟動應用程式,應用程式也能在離線時維持狀態。

只要一行程式碼,就能啟用磁碟持續性。

Swift

注意:這項 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 Realtime Database 會同步處理並儲存資料的本機副本,供活躍的事件監聽器使用。此外,您也可以同步處理特定位置。

Swift

注意:這項 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 用戶端會自動下載這些位置的資料,即使參照沒有任何有效的事件監聽器,也會保持同步。您可以使用下列程式碼行重新關閉同步功能。

Swift

注意:這項 Firebase 產品不適用於 App Clip 目標。
scoresRef.keepSynced(false)

Objective-C

注意:這項 Firebase 產品不適用於 App Clip 目標。
[scoresRef keepSynced:NO];

根據預設,系統會快取 10 MB 先前同步的資料。這對大多數應用程式來說應該足夠。如果快取超過設定的大小,Firebase Realtime Database 會清除最近最少使用的資料。保持同步的資料不會從快取中清除。

離線查詢資料

Firebase Realtime Database 會儲存查詢傳回的資料,以便在離線時使用。對於離線時建構的查詢,Firebase Realtime Database 會繼續處理先前載入的資料。如果要求的資料尚未載入,Firebase Realtime Database 會從本機快取載入資料。網路連線恢復後,系統就會載入資料並反映查詢。

舉例來說,以下程式碼會查詢分數 Firebase Realtime Database 中的最後四個項目

Swift

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

假設使用者失去連線、離線,並重新啟動應用程式。在離線狀態下,應用程式會從相同位置查詢最後兩個項目。這項查詢會成功傳回最後兩個項目,因為應用程式已在上述查詢中載入所有四個項目。

Swift

注意:這項 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 用戶端會使用永久快取,為分數最高的兩隻恐龍引發「child added」事件。但不會引發「value」事件,因為應用程式在連線時從未執行該查詢。

如果應用程式在離線時要求最後六個項目,就會立即收到四個快取項目的「child added」事件。裝置恢復連線後,Firebase Realtime Database 用戶端會與伺服器同步,並取得應用程式最後兩個「child added」和「value」事件。

離線處理交易

應用程式離線時執行的任何交易都會排入佇列。 應用程式重新連上網路後,交易就會傳送至 Realtime Database 伺服器。

管理在宅感應

在即時應用程式中,偵測用戶端何時連線和中斷連線通常很有幫助。舉例來說,您可能想在用戶端中斷連線時,將使用者標示為「離線」。

Firebase 資料庫用戶端提供簡單的原語,可讓您在用戶端與 Firebase 資料庫伺服器中斷連線時,寫入資料庫。無論用戶端是否正常中斷連線,系統都會執行這些更新,因此即使連線中斷或用戶端當機,您也可以放心讓系統清除資料。所有寫入作業 (包括設定、更新和移除) 都可以在中斷連線時執行。

以下是使用 onDisconnect 原始物件在中斷連線時寫入資料的簡單範例:

Swift

注意:這項 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 已正確附加:

Swift

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

您也可以呼叫 .cancel() 來取消 onDisconnect 事件:

Swift

注意:這項 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 用戶端的連線狀態變更時,該位置就會更新。範例如下:

Swift

注意:這項 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。

處理延遲時間

伺服器時間戳記

Firebase Realtime Database 伺服器提供一種機制,可將伺服器上產生的時間戳記做為資料插入。這項功能與 onDisconnect 搭配使用,可讓您輕鬆可靠地記錄 Realtime Database 用戶端中斷連線的時間:

Swift

注意:這項 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 用戶端新增至本機回報時間 (以毫秒為單位的 Epoch 時間) 的值 (以毫秒為單位),以估算伺服器時間。請注意,這個偏移值的準確度可能會受到網路延遲時間影響,因此主要用於找出時鐘時間的大幅差異 (超過 1 秒)。

Swift

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

Presence 範例應用程式

只要將中斷連線作業與連線狀態監控和伺服器時間戳記結合,即可建構使用者上線狀態系統。在這個系統中,每位使用者都會將資料儲存在資料庫位置,指出 Realtime Database 用戶端是否在線上。用戶端上線時會將這個位置設為 true,中斷連線時則會設定時間戳記。這個時間戳記代表指定使用者上次上線的時間。

請注意,應用程式應在使用者標示為上線前,將中斷連線作業排入佇列,以免在兩個指令傳送至伺服器前,用戶端網路連線中斷,導致發生任何競爭狀況。

以下是簡單的使用者上線狀態系統:

Swift

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