Firebase Summit で発表されたすべての情報をご覧ください。Firebase を使用してアプリ開発を加速し、自信を持ってアプリを実行する方法を紹介しています。詳細

Appleプラットフォームでのデータの読み取りと書き込み

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

(省略可)Firebase Local Emulator Suite でプロトタイプを作成してテストする

アプリが Realtime Database に対して読み取りと書き込みを行う方法について説明する前に、Realtime Database 機能のプロトタイプ作成とテストに使用できる一連のツール、Firebase Local Emulator Suite を紹介しましょう。さまざまなデータ モデルを試したり、セキュリティ ルールを最適化したり、バックエンドと対話するための最も費用対効果の高い方法を見つけようとしている場合、ライブ サービスをデプロイせずにローカルで作業できることは素晴らしいアイデアです。

Realtime Database エミュレーターは、ローカル エミュレーター スイートの一部であり、アプリがエミュレートされたデータベースのコンテンツと構成、および必要に応じてエミュレートされたプロジェクト リソース (関数、他のデータベース、およびセキュリティ ルール) とやり取りできるようにします。

Realtime Database エミュレーターを使用するには、いくつかの手順を実行する必要があります。

  1. アプリのテスト構成にコード行を追加して、エミュレーターに接続します。
  2. ローカル プロジェクト ディレクトリのルートから、 firebase emulators:startを実行します。
  3. 通常どおり Realtime Database プラットフォーム SDK を使用するか、Realtime Database REST API を使用して、アプリのプロトタイプ コードから呼び出しを行います。

Realtime Database と Cloud Functions に関する詳細なチュートリアルが利用可能です。 Local Emulator Suite Introductionも参照してください。

FIRDatabaseReference を取得する

データベースからデータを読み書きするには、 FIRDatabaseReferenceのインスタンスが必要です。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

書き込みデータ

このドキュメントでは、Firebase データの読み取りと書き込みの基本について説明します。

Firebase データはDatabase参照に書き込まれ、参照に非同期リスナーをアタッチすることで取得されます。リスナーは、データの初期状態に対して 1 回トリガーされ、データが変更されるたびに再度トリガーされます。

基本的な書き込み操作

基本的な書き込み操作では、 setValueを使用して指定した参照にデータを保存し、そのパスにある既存のデータを置き換えることができます。この方法を使用すると、次のことができます。

  • 使用可能な JSON タイプに対応するパス タイプは次のとおりです。
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

たとえば、次のようにsetValueを使用してユーザーを追加できます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

このようにsetValueを使用すると、子ノードを含め、指定された場所のデータが上書きされます。ただし、オブジェクト全体を書き換えなくても子を更新できます。ユーザーがプロファイルを更新できるようにしたい場合は、次のようにユーザー名を更新できます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

データの読み取り

値イベントをリッスンしてデータを読み取る

パスでデータを読み取り、変更をリッスンするには、FIRDatabaseReference のobserveEventType:withBlockを使用して、 FIRDatabaseReferenceイベントを観察FIRDataEventTypeValueます。

イベントタイプ典型的な使用法
FIRDataEventTypeValueパスの内容全体に対する変更を読み取り、リッスンします。

FIRDataEventTypeValueイベントを使用して、イベントの時点で存在する特定のパスにあるデータを読み取ることができます。このメソッドは、リスナーがアタッチされたときに 1 回トリガーされ、子を含むデータが変更されるたびに再度トリガーされます。イベント コールバックには、子データを含む、その場所にあるすべてのデータを含むsnapshotが渡されます。データがない場合、 exists()を呼び出すとスナップショットはfalseを返し、 valueプロパティを読み取るとnilを返します。

次の例は、データベースから投稿の詳細を取得するソーシャル ブログ アプリケーションを示しています。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

リスナーは、 valueプロパティのイベント時にデータベース内の指定された場所にあるデータを含むFIRDataSnapshotを受け取ります。 NSDictionaryなどの適切なネイティブ型に値を割り当てることができます。その場所にデータが存在しない場合、 valuenilです。

データを 1 回読み取る

getData() を使用して 1 回読み取る

SDK は、アプリがオンラインかオフラインかに関係なく、データベース サーバーとの対話を管理するように設計されています。

一般に、上記の値イベント手法を使用してデータを読み取り、バックエンドからデータへの更新の通知を受け取る必要があります。これらの手法により、使用量と請求額が削減され、オンラインとオフラインの両方でユーザーに最高のエクスペリエンスを提供するように最適化されます。

データが 1 回だけ必要な場合は、 getData()を使用してデータベースからデータのスナップショットを取得できます。何らかの理由でgetData()がサーバーの値を返すことができない場合、クライアントはローカル ストレージ キャッシュをプローブし、それでも値が見つからない場合はエラーを返します。

次の例は、ユーザーの公開ユーザー名をデータベースから 1 回取得する方法を示しています。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid];
[[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) {
  if (error) {
    NSLog(@"Received an error %@", error);
    return;
  }
  NSString *userName = snapshot.value;
}];

getData()を不必要に使用すると、帯域幅の使用が増加し、パフォーマンスが低下する可能性があります。これは、上記のリアルタイム リスナーを使用することで防ぐことができます。

オブザーバーでデータを 1 回読み取る

場合によっては、サーバーで更新された値を確認するのではなく、ローカル キャッシュの値をすぐに返したい場合があります。そのような場合、 observeSingleEventOfTypeを使用してローカル ディスク キャッシュからデータをすぐに取得できます。

これは、一度だけロードする必要があり、頻繁に変更されることやアクティブなリッスンを必要としないことが予想されるデータに役立ちます。たとえば、前の例のブログ アプリでは、ユーザーが新しい投稿の作成を開始するときに、このメソッドを使用してユーザーのプロファイルを読み込みます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in
  // Get user value
  let value = snapshot.value as? NSDictionary
  let username = value?["username"] as? String ?? ""
  let user = User(username: username)

  // ...
}) { error in
  print(error.localizedDescription)
}

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  // Get user value
  User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]];

  // ...
} withCancelBlock:^(NSError * _Nonnull error) {
  NSLog(@"%@", error.localizedDescription);
}];

データの更新または削除

特定のフィールドを更新する

他の子ノードを上書きせずにノードの特定の子に同時に書き込むには、 updateChildValuesメソッドを使用します。

updateChildValuesを呼び出すときに、キーのパスを指定することで、下位レベルの子の値を更新できます。スケーリングを改善するためにデータが複数の場所に保存されている場合は、データ ファンアウトを使用してそのデータのすべてのインスタンスを更新できます。たとえば、ソーシャル ブログ アプリでは、投稿を作成し、同時にそれを最近のアクティビティ フィードと投稿ユーザーのアクティビティ フィードに更新する必要がある場合があります。これを行うために、ブログ アプリケーションは次のようなコードを使用します。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
guard let key = ref.child("posts").childByAutoId().key else { return }
let post = ["uid": userID,
            "author": username,
            "title": title,
            "body": body]
let childUpdates = ["/posts/\(key)": post,
                    "/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
NSString *key = [[_ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"uid": userID,
                       @"author": username,
                       @"title": title,
                       @"body": body};
NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post,
                               [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post};
[_ref updateChildValues:childUpdates];

この例では、 childByAutoIdを使用して、 /posts/$postidにあるすべてのユーザーの投稿を含むノードに投稿を作成し、同時にgetKey()でキーを取得します。このキーを使用して、 /user-posts/$userid/$postidにあるユーザーの投稿に 2 番目のエントリを作成できます。

これらのパスを使用すると、 updateChildValuesを 1 回呼び出すだけで、JSON ツリー内の複数の場所に同時に更新を実行できます。たとえば、この例では両方の場所に新しい投稿を作成しています。この方法で行われる同時更新はアトミックです。すべての更新が成功するか、すべての更新が失敗します。

完了ブロックを追加する

データがいつコミットされたかを知りたい場合は、完了ブロックを追加できます。 setValueupdateChildValuesはどちらも、書き込みがデータベースにコミットされたときに呼び出されるオプションの完了ブロックを取ります。このリスナーは、どのデータが保存され、どのデータがまだ同期されているかを追跡するのに役立ちます。呼び出しが失敗した場合、失敗の原因を示すエラー オブジェクトがリスナーに渡されます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  if (error) {
    NSLog(@"Data could not be saved: %@", error);
  } else {
    NSLog(@"Data saved successfully.");
  }
}];

データを削除する

データを削除する最も簡単な方法は、そのデータの場所への参照に対してremoveValueを呼び出すことです。

setValueupdateChildValuesなどの別の書き込み操作の値としてnilを指定して削除することもできます。この手法をupdateChildValuesで使用して、1 回の API 呼び出しで複数の子を削除できます。

リスナーを切り離す

ViewControllerを離れても、オブザーバーはデータの同期を自動的に停止しません。オブザーバーが適切に削除されない場合、データは引き続きローカル メモリに同期されます。オブザーバーが不要になったら、関連するFIRDatabaseHandleremoveObserverWithHandleメソッドに渡して削除します。

コールバック ブロックを参照に追加すると、 FIRDatabaseHandleが返されます。これらのハンドルを使用して、コールバック ブロックを削除できます。

複数のリスナーがデータベース参照に追加されている場合、イベントが発生すると各リスナーが呼び出されます。その場所でデータの同期を停止するには、 removeAllObserversメソッドを呼び出して、その場所にあるすべてのオブザーバーを削除する必要があります。

リスナーでremoveObserverWithHandleまたはremoveAllObserversを呼び出しても、その子ノードに登録されているリスナーは自動的に削除されません。これらの参照またはハンドルを追跡して削除する必要もあります。

データをトランザクションとして保存

増分カウンターなど、同時変更によって破損する可能性があるデータを操作する場合は、トランザクション操作を使用できます。この操作には、更新関数とオプションの完了コールバックの 2 つの引数を指定します。更新関数は、データの現在の状態を引数として取り、書き込みたい新しい望ましい状態を返します。

たとえば、ソーシャル ブログ アプリの例では、次のように、ユーザーが投稿にスタ​​ーを付けたり、スターを外したり、投稿が獲得したスターの数を追跡したりできるようにすることができます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String: AnyObject],
    let uid = Auth.auth().currentUser?.uid {
    var stars: [String: Bool]
    stars = post["stars"] as? [String: Bool] ?? [:]
    var starCount = post["starCount"] as? Int ?? 0
    if let _ = stars[uid] {
      // Unstar the post and remove self from stars
      starCount -= 1
      stars.removeValue(forKey: uid)
    } else {
      // Star the post and add self to stars
      starCount += 1
      stars[uid] = true
    }
    post["starCount"] = starCount as AnyObject?
    post["stars"] = stars as AnyObject?

    // Set value and report transaction success
    currentData.value = post

    return TransactionResult.success(withValue: currentData)
  }
  return TransactionResult.success(withValue: currentData)
}) { error, committed, snapshot in
  if let error = error {
    print(error.localizedDescription)
  }
}

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
  NSMutableDictionary *post = currentData.value;
  if (!post || [post isEqual:[NSNull null]]) {
    return [FIRTransactionResult successWithValue:currentData];
  }

  NSMutableDictionary *stars = post[@"stars"];
  if (!stars) {
    stars = [[NSMutableDictionary alloc] initWithCapacity:1];
  }
  NSString *uid = [FIRAuth auth].currentUser.uid;
  int starCount = [post[@"starCount"] intValue];
  if (stars[uid]) {
    // Unstar the post and remove self from stars
    starCount--;
    [stars removeObjectForKey:uid];
  } else {
    // Star the post and add self to stars
    starCount++;
    stars[uid] = @YES;
  }
  post[@"stars"] = stars;
  post[@"starCount"] = @(starCount);

  // Set value and report transaction success
  currentData.value = post;
  return [FIRTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError * _Nullable error,
                       BOOL committed,
                       FIRDataSnapshot * _Nullable snapshot) {
  // Transaction completed
  if (error) {
    NSLog(@"%@", error.localizedDescription);
  }
}];

トランザクションを使用すると、複数のユーザーが同じ投稿に同時にスターを付けたり、クライアントのデータが古い場合に、スター カウントが正しくなくなるのを防ぐことができます。 FIRMutableDataクラスに含まれる値は、最初はクライアントが最後に認識したパスの値であり、存在しない場合はnilです。サーバーは初期値と現在の値を比較し、値が一致する場合はトランザクションを受け入れ、拒否します。トランザクションが拒否された場合、サーバーは現在の値をクライアントに返し、クライアントは更新された値でトランザクションを再度実行します。これは、トランザクションが受け入れられるか、試行回数が多すぎるまで繰り返されます。

サーバー側のアトミックインクリメント

上記の使用例では、データベースに 2 つの値を書き込んでいます。投稿に星を付けた/星を外したユーザーの ID と、増加した星の数です。ユーザーが投稿にスタ​​ーを付けていることがすでにわかっている場合は、トランザクションの代わりにアトミック インクリメント操作を使用できます。

迅速

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
let updates = [
  "posts/\(postID)/stars/\(userID)": true,
  "posts/\(postID)/starCount": ServerValue.increment(1),
  "user-posts/\(postID)/stars/\(userID)": true,
  "user-posts/\(postID)/starCount": ServerValue.increment(1)
] as [String : Any]
Database.database().reference().updateChildValues(updates);

Objective-C

注:この Firebase 製品は、App Clip ターゲットでは使用できません。
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1],
                        [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]};
[[[FIRDatabase database] reference] updateChildValues:updates];

このコードはトランザクション操作を使用しないため、競合する更新がある場合に自動的に再実行されることはありません。ただし、インクリメント操作はデータベース サーバーで直接行われるため、競合が発生する可能性はありません。

ユーザーが以前にスターを付けた投稿にスタ​​ーを付けるなど、アプリケーション固有の競合を検出して拒否する場合は、そのユース ケース用のカスタム セキュリティ ルールを作成する必要があります。

オフラインでデータを操作する

クライアントがネットワーク接続を失った場合でも、アプリは正常に機能し続けます。

Firebase データベースに接続されているすべてのクライアントは、アクティブなデータの独自の内部バージョンを維持します。データが書き込まれると、最初にこのローカル バージョンに書き込まれます。次に、Firebase クライアントは、そのデータをリモート データベース サーバーおよび他のクライアントと「ベスト エフォート」ベースで同期します。

その結果、データベースへのすべての書き込みは、データがサーバーに書き込まれる前に、すぐにローカル イベントをトリガーします。これは、ネットワークの遅延や接続に関係なく、アプリの応答性が維持されることを意味します。

接続が再確立されると、アプリは適切な一連のイベントを受け取り、クライアントが現在のサーバーの状態と同期するようにします。カスタム コードを記述する必要はありません。

オフラインの動作については、オンラインとオフラインの機能の詳細をご覧ください で詳しく説明します

次のステップ