アプリが一時的にネットワーク接続を失った場合でも、Firebase アプリケーションは動作します。さらに、Firebase は、データをローカルに永続化し、プレゼンスを管理し、レイテンシを処理するためのツールを提供します。
ディスクの永続性
Firebase アプリは、一時的なネットワークの中断を自動的に処理します。キャッシュされたデータはオフライン時に利用でき、Firebase はネットワーク接続が復元されたときに書き込みを再送信します。
ディスクの永続性を有効にすると、ユーザーまたはオペレーティング システムがアプリを再起動した場合でも、アプリはオフライン中に状態を維持できるように、データをデバイスにローカルに書き込みます。
わずか 1 行のコードでディスクの永続性を有効にできます。
迅速
Database.database().isPersistenceEnabled = true
Objective-C
[FIRDatabase database].persistenceEnabled = YES;
持続性の振る舞い
永続性を有効にすると、オンライン中に Firebase Realtime Database クライアントが同期するデータはすべてディスクに保持され、ユーザーまたはオペレーティング システムがアプリを再起動した場合でも、オフラインで使用できます。これは、キャッシュに保存されたローカル データを使用して、アプリがオンラインの場合と同じように動作することを意味します。リスナー コールバックは、ローカル アップデートに対して引き続き発生します。
Firebase Realtime Database クライアントは、アプリがオフラインの間に実行されるすべての書き込み操作のキューを自動的に保持します。永続化を有効にすると、このキューもディスクに永続化されるため、ユーザーまたはオペレーティング システムがアプリを再起動したときに、すべての書き込みを利用できるようになります。アプリが接続を回復すると、すべての操作が Firebase Realtime Database サーバーに送信されます。
アプリでFirebase Authenticationを使用している場合、Firebase Realtime Database クライアントはアプリの再起動後もユーザーの認証トークンを保持します。アプリがオフラインのときに認証トークンの有効期限が切れると、アプリがユーザーを再認証するまで、クライアントは書き込み操作を一時停止します。そうしないと、セキュリティ ルールが原因で書き込み操作が失敗する可能性があります。
データを最新に保つ
Firebase Realtime Database は、アクティブなリスナーのデータのローカル コピーを同期して保存します。さらに、特定の場所を同期することができます。
迅速
let scoresRef = Database.database().reference(withPath: "scores") scoresRef.keepSynced(true)
Objective-C
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"]; [scoresRef keepSynced:YES];
Firebase Realtime Database クライアントは、これらの場所にあるデータを自動的にダウンロードし、参照にアクティブなリスナーがない場合でも同期を維持します。次のコード行を使用して、同期をオフに戻すことができます。
迅速
scoresRef.keepSynced(false)
Objective-C
[scoresRef keepSynced:NO];
デフォルトでは、以前に同期された 10MB のデータがキャッシュされます。ほとんどのアプリケーションではこれで十分です。キャッシュが構成されたサイズを超えると、Firebase Realtime Database は最近使用されていないデータをパージします。同期が保たれているデータは、キャッシュから消去されません。
オフラインでのデータのクエリ
Firebase Realtime Database は、オフライン時に使用するために、クエリから返されたデータを保存します。オフライン中に作成されたクエリの場合、Firebase Realtime Database は以前に読み込まれたデータに対して引き続き機能します。リクエストされたデータが読み込まれていない場合、Firebase Realtime Database はローカル キャッシュからデータを読み込みます。ネットワーク接続が再び利用可能になると、データが読み込まれ、クエリが反映されます。
たとえば、次のコードは、Firebase Realtime Database のスコアの最後の 4 つの項目をクエリします。
迅速
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); }];
ユーザーが接続を失い、オフラインになり、アプリを再起動したとします。まだオフラインの間、アプリは同じ場所から最後の 2 つのアイテムを照会します。アプリは上記のクエリで 4 つの項目すべてを読み込んだため、このクエリは最後の 2 つの項目を正常に返します。
迅速
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 クライアントは、永続キャッシュを使用して、スコアが最も高い 2 つの恐竜の「子追加」イベントを発生させます。ただし、アプリはオンライン中にそのクエリを実行したことがないため、「値」イベントは発生しません。
アプリがオフライン中に最後の 6 つのアイテムを要求すると、キャッシュされた 4 つのアイテムの「子が追加されました」イベントがすぐに取得されます。デバイスがオンラインに戻ると、Firebase Realtime Database クライアントはサーバーと同期し、アプリの最後の 2 つの「child added」イベントと「value」イベントを取得します。
トランザクションのオフライン処理
アプリがオフラインのときに実行されるすべてのトランザクションは、キューに入れられます。アプリがネットワーク接続を回復すると、トランザクションが Realtime Database サーバーに送信されます。
プレゼンスの管理
リアルタイム アプリケーションでは、クライアントがいつ接続および切断するかを検出すると便利なことがよくあります。たとえば、クライアントが切断されたときにユーザーを「オフライン」としてマークしたい場合があります。
Firebase Database クライアントは、クライアントが Firebase Database サーバーから切断されたときにデータベースへの書き込みに使用できる単純なプリミティブを提供します。これらの更新は、クライアントが正常に切断されたかどうかにかかわらず発生するため、接続が切断されたり、クライアントがクラッシュしたりした場合でも、データをクリーンアップするために信頼できます。設定、更新、および削除を含むすべての書き込み操作は、切断時に実行できます。
onDisconnect
プリミティブを使用して、切断時にデータを書き込む簡単な例を次に示します。
迅速
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 クライアントによってアクティブに閉じられた場合、サーバーは (操作がまだ有効であることを確認するために) セキュリティを 2 回チェックしてから、イベントを呼び出します。
アプリは書き込み操作でコールバックを使用して、 onDisconnect
が正しくアタッチされたことを確認できます。
迅速
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()
を呼び出してキャンセルすることもできます:
迅速
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 は、Firebase Realtime Database クライアントの接続状態が変化するたびに更新される/.info/connected
という特別な場所を提供します。次に例を示します。
迅速
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 クライアントが切断された時刻を確実に記録する簡単な方法が提供されます。
迅速
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 秒を超える) 不一致を検出するのに役立ちます。
迅速
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 に設定し、切断するとタイムスタンプを設定します。このタイムスタンプは、特定のユーザーが最後にオンラインだった時間を示します。
両方のコマンドがサーバーに送信される前にクライアントのネットワーク接続が失われた場合に競合状態を回避するために、ユーザーがオンラインとしてマークされる前に、アプリは切断操作をキューに入れる必要があることに注意してください。
以下は、単純なユーザー プレゼンス システムです。
迅速
// 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 let connected = snapshot.value as? Bool, connected 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]]; } }];