【Swift UI/Firebase】Realtime Databaseの導入方法と使い方!データの取得
この記事からわかること
- Swift/Firebaseで作成したiOSアプリにRealtime Databaseを導入する方法
- Swift UIでCocoa Podsを使用している場合のインストール方法
- データベースの作成と書き込み/読み取りなどの操作方法
- setValueメソッドやupdateChildValues、observeSingleEvent、getDataの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
Firebaseの概要や登録方法については下記記事を参考にしてください。
Realtime Databaseとは?
Firebase Realtime Databaseとはリアルタイムでデータを保存してユーザー間で同期することができるクラウドホスト型NoSQLデータベースです。
Webアプリとモバイルアプリに同じデータを表示させたり、異なるユーザー同士に同じデータを表示させることで共有可能なアプリを作成することができるようになります。
またFirebase Realtime Databaseはデータをサーバーとローカルの2箇所に保持します。ローカルにデータをキャッシュすることでオフラインになってもデータ表示を継続して行えるようになっており、再びオンラインに戻った際に自動でデータを同期してくれます。
アプリからの操作は基本的にはローカルに書き込み処理を行い、サーバーと接続できるタイミングでサーバーと同期します。
データの管理方法
データの保存先は3箇所から選択できるようになっています。同じプロジェクト内でも異なるロケーションにデータベースを構築することができるようです。
- 米国
- ベルギー
- シンガポール
そして実際のデータはJSON形式で蓄積、管理されていきます。テーブルやレコードではなく、ツリー上になったJSON形式の構造でデータは管理されます。そのため入れ子にできるのは32レベルまでと制限が設けられています。
iOSアプリにRealtime Databaseを導入する流れ
公式ドキュメント:Realtime Databaseのセットアップ方法
流れ
- Firebaseプロジェクトを作成
- データベースの作成
- iOSアプリの登録
- GoogleService-Info.plistの追加
- SDKの導入
- 初期化コードの組み込み
- 完了
ここでは通常の「Firebaseプロジェクトを作成」や「iOSアプリの登録」の手順は割愛しています。詳細は以下の記事を参考にしてください。
【Swift/Xcode】Firebaseの導入方法!iOSアプリでの使い方
データベースの作成
Firebaseプロジェクトを作成したら、プロジェクト内にデータベースを作成しておきます。左側メニューの「Realtime Database」>「データベースを作成」をクリックします。
続いてロケーションを問われるので「米国」にして進めていきます。(※米国ではなく東京(asia-northeast1)か大阪(asia-northeast2)のが良いかもです。)
データベースの設定は本番環境であればロックモードで、練習であればテストモードにチェックを入れて「有効にする」をクリックします。
ロックモードを指定した場合はクライアントがデータの読み取り/書き取りができない状態になっているので「ルール」のfalse
をtrue
に変更しておきます。
ここまできたらプロジェクトにiOSアプリを登録していきます。
さらに「GoogleService-Info.plist をダウンロード」をクリックし、ダウンロードした「GoogleService-Info.plist」をドラッグ&ドロップでアプリに入れ込みます。
「Copy items if needed」と「Create groups」にチェックを入れて「Finish」をクリックします。
Realtime Database SDKの導入
「Cocoa Pods」を使用して「Realtime Database SDK」を導入していきます。「PodFile」に以下の一文を追記してpod install
を実行します。
pod 'FirebaseDatabase'
おすすめ記事:【Swift UI】CocoaPodsのインストール方法と使い方!
最後にアプリのエントリポイント部分に初期化のためのコードを記述していきます。
import SwiftUI
import FirebaseCore // 追加
// 追加
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
@main
struct TestFirebaseApp: App {
// 追加
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
これで指定のiOSアプリからRealtime Databaseを使用できるようにすることができました。
使用方法
ここからはiOSアプリ内でRealtime Databaseを操作する方法をまとめていきます。iOSアプリから操作するとリアルタイムでデータベース内のデータが変更されていくのを「データ」の中から確認することができます。
データベースへの参照
まずはFIRDatabaseReference
インスタンスを生成してRealtime Databaseへ参照できるようにする必要があります。FirebaseDatabase
をインポートすることでDatabase.database().reference()
が使用できるようになり、ここからインスタンスを生成できます。
import FirebaseDatabase
var ref: DatabaseReference! = Database.database().reference()
データの書き込み処理
データの書き込み処理はsetValue
メソッドを使用します。引数にはNSString
、NSNumber
、NSDictionary
、NSArray
型の値を渡すことができます。child
メソッドを使用することでネストさせることができます。
ref.child("users").setValue(["username": "ame"])
例えば上記のコードを実行するとデータベース内は以下のようになります。
データの更新処理
データの更新処理はsetValue
メソッドを使用して上書きすることができます。また先ほどと同様の値を変更するには以下のようにchild
をメソッドチェーンでつなぐ方法や「/」で区切って指定することも可能です。以下は全て同じ深さのデータを変更しています。
ref.child("users").setValue(["username": "kasa"])
ref.child("users").child("username").setValue("kasa")
ref.child("users/username").setValue("kasa")
しかしsetValue
メソッドは指定した階層の値を新規で追加(あれば上書き)するためその階層より下に階層があっても消えてしまいます。指定した階層の値だけ書き換えるにはupdateChildValues
メソッドを使用します。
updateChildValues
データを更新するにはupdateChildValues
メソッドを使用した方が良さそうです。任意の深さでメソッドを呼び出すことで引数に指定した値に更新することができます。
ref.child("users").child("username").updateChildValues("kasa")
また引数に辞書形式でキーパスと値を複数渡すことで一度に複数箇所のデータを更新することも可能です。
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)
自動でIDを付与する
childByAutoId
メソッドを使用することで一意となる文字列を付与することができます。
ref.child("users").childByAutoId().setValue("ame")
また生成される文字列はタイムスタンプを元にしているためソートをかけると時系列に並べることができます。
データの削除処理
データの削除処理はremoveValue
メソッドを使用します。指定した値より子要素があった場合はその子要素も全て削除されます。
ref.child("users").removeValue()
また書き込み処理ができるsetValue
やupdateChildValues
にnil
を渡すことで削除することも可能です。その場合は一度に複数箇所のデータを削除することもできます。
データの読み取り処理
データの読み取り処理は書き込みや削除よりはややこしくなっています。Realtime Databaseではサーバー側とローカルのキャッシュ側にデータを保持しているのでどちらのデータを読みに行くかを指定することができるようになっています。
この状況の場合で読み取り処理を行なってみます。
getDataメソッド
getData
は1回だけデータを読み取るメソッドです。基本的にはサーバーの値を読みにいき、オフライン環境などによってサーバーの値の取得に失敗した場合はローカルキャッシュの値を読みにいきます。
実装コードを見てみるといきなり行数が増えました。読み解いていくと最初の入れ子を掘り進める過程は同じです。値を取り出したい深さまで到達したらgetData
メソッドを呼び出します。引数のcompletionHandler
からエラーと、snapshot(FIRDataSnapshot型)にアクセスすることができます。(スナップショットとは「その時点のもの」といった意味の英単語です)
おすすめ記事:【Swift】completionHandlerとは?使い方と@escapingの意味
ref.child("users").getData(completion: { error, snapshot in
guard error == nil else {
print(error!.localizedDescription)
return
}
if let dic = snapshot?.value as? [String:AnyObject]{
self.userName = dic["username"] as? String ?? "Unknown"
}
});
value
プロパティから値にアクセスし適切な型(NSDictionary型:[String:AnyObject]など)に変換してその中に参照します。
データを明示的に1回読みにいくこのメソッドは多用すると帯域幅の使用が増加し、パフォーマンスが低下する可能性があります。
observeSingleEvent
ローカルキャッシュの値を読み込むにはobserveSingleEvent
メソッドを使用します。リアルタイムでの変更の反映が必要ないデータなどを取得する際はこちらを使用します。
ref.child("users").observeSingleEvent(of: .value, with: { snapshot in
guard error == nil else {
print(error!.localizedDescription)
return
}
if let dic = snapshot?.value as? [String:AnyObject]{
self.userName = dic["username"] as? String ?? "Unknown"
}
});
observe
サーバーにあるデータを観測し、常に更新されたデータを取得するためにはobserve
メソッドを使用します。このメソッドは初期の呼び出し時とサーバーの値が更新されたタイミングに毎回実行されます。しかしツリー状のルートを指定し呼び出すと全ての変更を観測することになるため、必要な深さまでを指定しアタッチするのが推奨されています。
ref.child("users").observe(DataEventType.value, with: {
print("値が変更されました: \(snapshot.value as AnyObject)")
}
コールバック処理の設置
データを書き込む処理には成功か失敗かをコールバックとして受け取れるようにすることができます。
ref.child("users").setValue(["username": ame]) { (error:Error?, ref:DatabaseReference) in
if let error = error {
print("失敗: \(error).")
} else {
print("データの保存に成功しました!")
}
}
オフライン環境でデータをキャッシュする
オフライン環境でもデータを永続的に表示できるようにするためには明示的に設定をする必要があります。
Database.database().isPersistenceEnabled = true
詳細な設定方法は下記記事を参考にしてください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。