Cloud Firestore(ベータ版)の発表: Firebase と Google Cloud Platform から新しいスケーラブルで、フレキシブルなデータベースを利用できるようになりました。詳しくは、Cloud Firestore のドキュメントをご覧ください。

iOS でのデータの読み取りと書き込み

FIRDatabaseReference の取得

データベースでデータの読み書きを行うには、次に示す FIRDatabaseReference のインスタンスが必要です。

Swift

var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

@property (strong, nonatomic) FIRDatabaseReference *ref;

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

データの読み取りと書き込み

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

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

基本的な書き込みオペレーション

基本的な書き込みオペレーションには、setValue を使用してデータを特定の参照に保存できます。そのパスにある既存のデータが置換されます。このメソッドを使用して行える操作は次のとおりです。

  • 次のような、使用可能な JSON 型に対応する型を渡します。
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

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

Swift

self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

setValue をこの方法で使用すると、特定の場所にあるデータ(子ノードも含む)が上書きされます。ただし、オブジェクト全体を書き換えずに子を更新することもできます。ユーザーに自分のプロフィールの更新を許可する場合、次のように username を更新できます。

Swift

self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

value イベントのリッスン

パスにあるデータを読み取り、変更をリッスンするには、FIRDatabaseReferenceobserveEventType:withBlock メソッドまたは observeSingleEventOfType:withBlock メソッドを使用して、FIRDataEventTypeValue イベントを監視します。

イベントの種類 一般的な使用方法
FIRDataEventTypeValue パスのコンテンツ全体に対する変更の読み取りとリッスンを行います。

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

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

Swift

refHandle = postRef.observe(DataEventType.value, with: { (snapshot) in
  let postDict = snapshot.value as? [String : AnyObject] ?? [:]
  // ...
})

Objective-C

_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

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

データの 1 回読み取り

変更を想定していない UI 要素を初期化するときなど、コールバックを 1 回呼び出してから直ちに削除したい場合があります。observeSingleEventOfType メソッドを使用して、このシナリオを簡素化できます。追加されたイベント コールバックは 1 回トリガーされ、その後再びトリガーされることはありません。

これは 1 回読み込む必要があるだけで頻繁な変更やアクティブなリッスンを行うことは想定していないデータに対して有用です。たとえば、以前の例にあるブログアプリでは、このメソッドを使用して、ユーザーが新しい投稿を作成し始めたときにユーザーのプロフィールを読み込んでいます。

Swift

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

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 の呼び出し時に、キーのパスを指定して下位レベルの子の値を更新できます。スケーラビリティを向上させるためにデータが複数の場所に保存されている場合、データのファンアウトを使用してそのデータのすべてのインスタンスを更新できます。たとえば、ソーシャル ブログ アプリで、投稿を作成して、それと同時にその投稿から最近のアクティビティ フィードと投稿ユーザーのアクティビティ フィードを更新するとします。これを実現するには、ブログアプリで次のようなコードを使用します。

Swift

let key = ref.child("posts").childByAutoId().key
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

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 にあるユーザーの投稿に別のエントリを作成できます。

これらのパスを使用すると、上記の例で両方の場所に新しい投稿を作成したように、updateChildValues を 1 回呼び出すだけで JSON ツリー内の複数の場所に対して更新を同時に実行できます。この方法による同時更新はアトミック(不可分)です。つまり、すべての更新が成功するか、すべての更新が失敗するかのどちらかです。

完了ブロックの追加

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

Swift

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

[[[_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 を併用すると、API を 1 回呼び出すだけで複数の子を削除できます。

リスナーのデタッチ

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

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

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

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

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

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

たとえば、このソーシャル ブログ アプリの例では、次のようにして、投稿にスターを付ける / 投稿のスターを取り消す操作をユーザーに許可し、投稿で得られたスターの数を追跡できます。

Swift

ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
    var stars: Dictionary<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

[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 になります。サーバーは、この初期値をその現在の値と比較し、値が一致した場合はトランザクションを受け入れ、そうでない場合は拒否します。トランザクションが拒否された場合、サーバーは現在の値をクライアントに返します。クライアントは更新された値でトランザクションを再度実行します。この処理は、トランザクションが受け入れられるか、試行が上限の回数に達するまで繰り返されます。

オフラインでのデータの書き込み

クライアントでネットワーク接続が切断された場合でも、アプリは引き続き適切に機能します。

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

その結果、データベースへの書き込みが発生すると、実際にサーバーへデータが書き込まれるよりも早く、ローカル イベントが直ちにトリガーされます。つまり、ネットワークのレイテンシや接続に関係なく、アプリは応答性の高い状態を維持します。

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

次のステップ

フィードバックを送信...

Firebase Realtime Database
ご不明な点がありましたら、Google のサポートページをご覧ください。