即使應用程式暫時失去網路連線,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 驗證,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 MB 的先前同步資料。這應該足以滿足大多數應用程式的需求。如果快取超出設定大小,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); } }];
您也可以呼叫 .cancel()
取消 onDisconnect
事件:
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]];
時鐘偏差
雖然 firebase.database.ServerValue.TIMESTAMP
的準確度高出許多,且是大多數讀取/寫入作業的首選,但有時估算用戶端時鐘相對於 Firebase Realtime Database 伺服器的時鐘偏差,也可能很有用。您可以將回呼附加至位置 /.info/serverTimeOffset
,取得 Firebase Realtime Database 用戶端新增至本機回報時間 (以毫秒為單位的紀元時間) 的值 (以毫秒為單位),藉此估算伺服器時間。請注意,網路延遲可能會影響這個偏移量的準確度,因此主要用於發現時鐘時間的大幅差異 (大於 1 秒)。
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); }];
範例 Presence 應用程式
結合中斷連線作業、連線狀態監控和伺服器時間戳記,即可建構使用者狀態系統。在這個系統中,每位使用者都會在資料庫位置儲存資料,指出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]]; } }];