【Swift】iOSアプリでFirestoreからデータを取得する方法


昔、AWSを使って個人アプリを開発していたのですが、APIを作ってEC2に配置して、RDS設定して・・・
とにかく色々大変だったので、もっと簡単にCRUD機能を実現したいなと思いました

今更かもしれませんが、FirestoreでAPIを作らずに簡単にCRUD機能を実現できることを知ったので、今回はデータを取得するところまでをやってみたいと思います

ソースコードはこちらから↓

環境

Xcode12.4
Swift5

Firebaseと連携する

下記の公式ドキュメントに沿って、iOSアプリとFirebsseの連携をしてみる

新規プロジェクト作成

Firebaseコンソール画面から新規にプロジェクトを追加する

プロジェクト追加

任意のプロジェクト名を記載して続行

今回はGoogleアナリティクスへの連携はOFFにして、プロジェクトを作成
ONにすると、次の画面で連携するGoogleアカウントを指定する必要があります

新規にプロジェクトが作成されました

Firestoreでデータベース作成

早速、FireStoreでデータを作成していきます

コンソール画面からFirestore Databaseを選択してデータベースを作成

テストモードを選択して次に行きます

公式ドキュメントによると、本番環境モード(ドキュメントではロックモード)では、モバイルアプリから直接Firestoreのデータを参照できないため、APIやBatchを経由する必要があるみたいです。
そのため、今回はモバイルアプリから直接データを参照できるテストモードを選択します

ロックモード
モバイルおよびウェブ クライアントからのすべての読み書きを拒否します。認証されたアプリケーション サーバー(C#、Go、Java、Node.js、PHP、Python、Ruby)は引き続きデータベースにアクセスできます。
https://firebase.google.com/docs/firestore/quickstart?hl=ja#create

ロケーションをは利用場所から一番近いasia-northeast1(東京)を選択します

日本にはロケーションが東京と大阪にあるみたいです
ロケーションは一度しか設定できないため要注意です

asia-northeast1 東京
asia-northeast2 大阪
https://firebase.google.com/docs/firestore/locations?hl=ja#types

データ追加

今回はコンソール画面からPersonデータを2件追加しました

Firestoreだと配列の中に配列を入れることができないため、今回はhobbies配列の中にmapタイプでnameやyearのデータを入れました。referenceタイプを指定すると、別のドキュメントを指定することも可能です。

ちなみに、コレクションやドキュメントの説明については、以下の記事がとてもわかりやすかったです

ライブラリをインストール

Firestoreのライブラリを入れていきます。今回はCocoaPodsを利用します。
Podsの初期化については以下の記事が参考になると思います。

指定のライブラリをPodfileに追加します
※Swiftを利用する場合は、FirebaseFirestoreSwiftも追加するらしい。

pod 'Firebase/Firestore'
# Optionally, include the Swift extensions if you're using Swift.
pod 'FirebaseFirestoreSwift'

インストールします

pod install

Firebseの初期化

Firestoreを利用する際に初期化が必要となるため、Appdelegateで初期化しておきます

import UIKit
import Firebase

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Firebase初期化
        FirebaseApp.configure()
        return true
    }
}

ビルドしたらコンパイルエラーになりました・・・

Terminating app due to uncaught exception 'com.firebase.core', reason: '`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find a valid GoogleService-Info.plist in your project. Please download one from https://console.firebase.google.com/.'

そもそもFirebaseにiOSプロジェクトを追加していなかったです
初期設定の際にGoogleService-Info.plistを追加するのですが、今回はそのファイルがなくてエラーとなっています

FirebaseにiOSプロジェクト追加

少し話が前後してしまい申し訳ないですが、FirebaseにiOSプロジェクトを追加します

コンソール画面でiOSを選択

BundleIDとニックネームを記載

GoogleService-Info.plistをダウンロード
後続は前述したようにpodでインストールとFirebaseの初期化処理なので、割愛します

GoogleService-Info.plistをプロジェクトのRoot配下に追加

無事にビルドできました(失礼しました!)

Firestoreからデータ取得

Firestoreで追加したデータを構造体(Person,Hobby)に格納しました
データは非同期で取得されます

ViewController.swift
import UIKit
import Firebase

struct Person {
    var name: String
    var age: Int
    var hobbys: [Hobby]
}

struct Hobby {
    var name: String
    var year: Int
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // リスト初期化
        var persons: [Person] = []

        // FirestoreのDB取得
        let db = Firestore.firestore()
        // personsコレクションを取得
        db.collection("persons").getDocuments() { collection, err in
            // エラー発生時
            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                // コレクション内のドキュメントを取得
                for document in collection!.documents {
                    // hobbiesフィールドを取得
                    guard let hobbyDicList: [[String : Any]] = document.get("hobbies") as? [[String : Any]] else {
                        continue
                    }

                    // リスト初期化
                    var hobbies: [Hobby] = []

                    // hobbies内のフィールドを取得
                    for hobbyDic in hobbyDicList {
                        guard let hobbyName = hobbyDic["name"] as? String ,
                              let hobbyYear = hobbyDic["year"] as? Int else {
                            continue
                        }
                        // Hobbyを作成
                        let hobby = Hobby(name: hobbyName, year: hobbyYear)
                        // リストに追加
                        hobbies.append(hobby)
                    }

                    // Personフィールドを取得
                    guard let personName = document.get("name") as? String,
                          let personAge = document.get("age") as? Int else {
                        continue
                    }

                    // Personを作成
                    let person = Person(name: personName, age: personAge, hobbys: hobbies)

                    // リストに追加
                    persons.append(person)
                }
            }
            // コンソール出力
            print(persons)
        }
    }
}

出力結果より、Firestoreに追加したデータを無事に取得することができました!

[firestore_sample_app.Person(name: "jiro", age: 35, hobbys: [firestore_sample_app.Hobby(name: "climbing", year: 10)]), firestore_sample_app.Person(name: "taro", age: 20, hobbys: [firestore_sample_app.Hobby(name: "baseball", year: 5), firestore_sample_app.Hobby(name: "tennis", year: 8)])]

おわりに

今回、Firestoreを利用してみましたが、とても使いやすかったです
シンプルな機能ならFirebaseだけで事足りそうだなと感じました

ユーザー認証、ストレージ機能など他にも様々な機能が用意されているので、色々試してみたいと思います
(サーバ側はFirebaseに任せてiOSアプリに注力できそうです)

最後まで読んで頂きありがとうございます!