sql >> データベース >  >> RDS >> Database

iOS用CloudFirestore入門

    モバイルコーダーは、Googleのサービスとしてのモバイルバックエンド(MBaaS)プラットフォームであるFirebase Realtime Databaseを長年利用しており、バックエンドのインフラストラクチャやデータベースについて心配することなく、アプリの機能の構築に集中できるようにしています。 Firebaseを使用すると、データをクラウドに簡単に保存して永続化し、認証とセキュリティを管理できるため、コーダーはクライアント側に集中できます。

    昨年、Googleはさらに別のバックエンドデータベースソリューションであるCloud Firestoreを発表しました。これは、スケーラビリティと直感性の向上を約束してゼロから構築されたものです。ただし、これにより、Googleの既存の主力製品であるFirebaseRealtimeDatabaseとの関係で混乱が生じました。このチュートリアルでは、2つのプラットフォームの違いとそれぞれの明確な利点について概説します。簡単なリマインダーアプリを作成することで、Firestoreドキュメント参照の操作方法、およびデータの読み取り、書き込み、更新、削除をリアルタイムで行う方法を学習します。

    このチュートリアルの目的

    このチュートリアルでは、CloudFirestoreについて説明します。プラットフォームを活用してデータベースの永続性と同期をリアルタイムで実現する方法を学習します。次のトピックについて説明します。

    • CloudFirestoreとは
    • Firestoreデータモデル
    • CloudFirestoreのセットアップ
    • CloudFirestoreの参照を作成して操作する
    • CloudFirestoreからリアルタイムでデータを読み取る
    • データの作成、更新、削除
    • フィルタリングと複合クエリ

    想定される知識

    このチュートリアルは、Firebaseにある程度触れ、SwiftとXcodeで開発した背景があることを前提としています。

    Cloud Firestoreとは何ですか?

    Firebase Realtime Databaseと同様に、Firestoreは、モバイルデベロッパーとウェブデベロッパーに、ネットワークレイテンシやインターネット接続に関係なく、データをリアルタイムで永続化するクロスプラットフォームクラウドソリューションと、GoogleCloudPlatform製品スイートとのシームレスな統合を提供します。これらの類似点に加えて、互いに差別化する明確な長所と短所があります。

    データモデル

    基本的なレベルでは、Realtime Databaseはデータを1つの大きなモノリシックな階層型JSONツリーとして格納しますが、Firestoreはデータをドキュメントとコレクション、およびサブコレクションに編成します。これにより、非正規化が少なくて済みます。 1つのJSONツリーにデータを格納すると、単純なデータ要件を処理する場合に単純になるという利点があります。ただし、より複雑な階層データを操作する場合は、大規模な処理が煩雑になります。

    オフラインサポート

    どちらの製品もオフラインサポートを提供し、ネットワーク接続が潜在的またはまったくない場合にデータをキューにアクティブにキャッシュし、可能な場合はローカルの変更をバックエンドに同期します。 Firestoreはモバイルアプリに加えてウェブアプリのオフライン同期をサポートしていますが、RealtimeDatabaseはモバイル同期のみを有効にします。

    クエリとトランザクション

    Realtime Databaseは、制限された並べ替えおよびフィルタリング機能のみをサポートします。単一のクエリで、プロパティレベルでのみ並べ替えまたはフィルタリングを実行できますが、両方をサポートすることはできません。クエリも深く、結果の大きなサブツリーを返します。この製品は、完了コールバックを必要とする単純な書き込みおよびトランザクション操作のみをサポートします。

    一方、Firestoreでは、複合並べ替えとフィルタリングを使用したインデックスクエリが導入されており、アクションを組み合わせてチェーンフィルタと並べ替えを作成できます。 Realtime Databaseで取得するコレクション全体の代わりに、サブコレクションを返す浅いクエリを実行することもできます。トランザクションは、バッチ操作を送信する場合でも単一の操作を送信する場合でも、本質的にアトミックであり、トランザクションは完了するまで自動的に繰り返されます。さらに、Realtime Databaseは個別の書き込みトランザクションのみをサポートしますが、Firestoreはアトミックにバッチ操作を提供します。

    パフォーマンスとスケーラビリティ

    ご想像のとおり、リアルタイムデータベースは非常に堅牢で、レイテンシが低くなっています。ただし、データベースは、ゾーンの可用性に応じて、単一のリージョンに制限されます。一方、Firestoreは、真のグローバルな可用性、スケーラビリティ、および信頼性を確保するために、複数のゾーンおよびリージョンにわたってデータを水平方向に格納します。実際、Googleは、FirestoreがRealtimeDatabaseよりも信頼性が高いことを約束しています。

    リアルタイムデータベースのもう1つの欠点は、100,000の同時ユーザー(単一のデータベースで100,000の同時接続と1,000の書き込み/秒)に制限されていることです。その後、より多くのユーザーをサポートするには、データベースをシャーディング(データベースを複数のデータベースに分割)する必要があります。 。 Firestoreは、介入しなくても、複数のインスタンスに自動的にスケーリングします。

    スケーラビリティを念頭に置いてゼロから設計されたFirestoreは、複数のリージョン間でデータを複製し、認証を処理し、その他のセキュリティ関連の問題をすべてクライアント側のSDK内で処理する新しい回路図アーキテクチャを備えています。その新しいデータモデルは、Firebaseよりも直感的で、MongoDBなどの他の同等のNoSQLデータベースソリューションによく似ていますが、より堅牢なクエリエンジンを提供します。

    セキュリティ

    最後に、Realtime Databaseは、以前のチュートリアルでご存知のように、個別の検証トリガーを使用してルールをカスケードすることでセキュリティを管理します。これはFirebaseデータベースルールで機能し、データを個別に検証します。一方、Firestoreは、CloudFirestoreのセキュリティルールとIDおよびアクセス管理(IAM)を利用して、データ検証を自動的に除外する、よりシンプルで強力なセキュリティモデルを提供します。

    • モバイル開発FirebaseセキュリティルールChikeMgbemena

    Firestoreデータモデル

    FirestoreはNoSQLドキュメントベースのデータベースであり、各ドキュメントにデータが含まれているドキュメントのコレクションで構成されています。これはNoSQLデータベースであるため、リレーショナルデータベースにあるテーブル、行、その他の要素を取得するのではなく、ドキュメント内にあるキーと値のペアのセットを取得します。

    ドキュメントにデータを割り当てることで暗黙的にドキュメントとコレクションを作成します。ドキュメントまたはコレクションが存在しない場合、コレクションは常にルート(最初の)ノードである必要があるため、自動的に作成されます。これは、まもなく作業するプロジェクトの簡単なTasksサンプルスキーマであり、Tasksコレクションと、名前(文字列)、およびタスクが完了したかどうかのフラグ(ブール値)の2つのフィールドを含む多数のドキュメントで構成されます。 。

    それぞれの要素を分解して、理解を深めましょう。

    コレクション

    SQLの世界ではデータベーステーブルと同義であり、コレクションには1つ以上のドキュメントが含まれます。コレクションはスキーマのルート要素である必要があり、ドキュメントのみを含めることができ、他のコレクションを含めることはできません。ただし、コレクション(サブコレクション)を参照するドキュメントを参照することはできます。

    上の図では、タスクは2つのプリミティブフィールド(名前と完了)と、それ自体の2つのプリミティブフィールドで構成されるサブコレクション(サブタスク)で構成されています。

    ドキュメント

    ドキュメントはキーと値のペアで構成され、値は次のいずれかのタイプになります。

    • プリミティブフィールド(文字列、数値、ブール値など)
    • 複雑なネストされたオブジェクト(プリミティブのリストまたは配列)
    • サブコレクション

    ネストされたオブジェクトはマップとも呼ばれ、ドキュメント内で次のように表すことができます。次に、ネストされたオブジェクトと配列の例をそれぞれ示します。

    ID: 2422892 //primitive
    name: “Remember to buy milk” 
    detail: //nested object
        notes: "This is a task to buy milk from the store"
    	created: 2017-04-09
    	due: 2017-04-10
    done: false
    notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"]
    ...

    サポートされているデータ型の詳細については、Googleのデータ型のドキュメントを参照してください。次に、CloudFirestoreと連携するプロジェクトを設定します。

    プロジェクトの設定

    以前にFirebaseを使用したことがある場合は、これの多くはおなじみのはずです。それ以外の場合は、Firebaseでアカウントを作成し、前のチュートリアル「iOS向けFirebase認証の開始」の「プロジェクトの設定」セクションの手順に従う必要があります。

    このチュートリアルに従うには、チュートリアルプロジェクトリポジトリのクローンを作成します。次に、によってFirestoreライブラリを含めます ポッドファイルに以下を追加します :

    pod 'Firebase/Core' 
    pod 'Firebase/Firestore'

    ライブラリを構築するには、ターミナルに次のように入力します。

    pod install

    次に、Xcodeに切り替えて、 .xcworkspaceを開きます。 ファイル。 AppDelegate.swiftに移動します ファイルを作成し、application:didFinishLaunchingWithOptions:内に次のように入力します 方法:

    FirebaseApp.configure()

    ブラウザでFirebaseコンソールに移動し、データベースを選択します 左側のタブ。

    必ずテストモードで開始するオプションを選択してください 実験中にセキュリティの問題が発生しないようにし、アプリを本番環境に移行する際はセキュリティに関する通知に注意してください。これで、コレクションといくつかのサンプルドキュメントを作成する準備が整いました。

    コレクションとサンプルドキュメントの追加

    まず、最初のコレクションであるTasksを作成します。 、コレクションの追加を選択します 以下に示すように、ボタンを押してコレクションに名前を付けます。

    最初のドキュメントでは、ドキュメントIDを空白のままにします。これにより、IDが自動生成されます。ドキュメントは、nameの2つのフィールドで構成されます。 およびdone

    ドキュメントを保存すると、自動生成されたIDとともにコレクションとドキュメントを確認できるはずです:

    クラウドでサンプルドキュメントを使用してデータベースを設定すると、XcodeでFirestoreSDKの実装を開始する準備が整います。

    データベース参照の作成と操作

    MasterViewController.swiftを開きます Xcodeでファイルを作成し、次の行を追加してライブラリをインポートします。

    import Firebase
    
    class MasterViewController: UITableViewController {
        @IBOutlet weak var addButton: UIBarButtonItem!
        
        private var documents: [DocumentSnapshot] = []
        public var tasks: [Task] = []
        private var listener : ListenerRegistration!
       ...

    ここでは、変更があったときにデータベースへの接続をリアルタイムでトリガーできるリスナー変数を作成しているだけです。 DocumentSnapshotも作成しています 一時データスナップショットを保持する参照。

    ビューコントローラを続行する前に、別のswiftファイル Task.swiftを作成します 、データモデルを表します:

    import Foundation
    
    struct Task{
        var name:String
        var done: Bool
        var id: String
        
        var dictionary: [String: Any] {
            return [
                "name": name,
                "done": done
            ]
        }
    }
    
    extension Task{
        init?(dictionary: [String : Any], id: String) {
            guard   let name = dictionary["name"] as? String,
                let done = dictionary["done"] as? Bool
                else { return nil }
            
            self.init(name: name, done: done, id: id)
        }
    }

    上記のコードスニペットには、モデルオブジェクトへの入力を容易にする便利なプロパティ(ディクショナリ)とメソッド(init)が含まれています。ビューコントローラに戻り、ベースクエリをタスクリストの上位50エントリに制限するグローバルセッター変数を宣言します。 didSetに示されているように、クエリ変数を設定すると、リスナーも削除されます。 以下のプロパティ:

    fileprivate func baseQuery() -> Query {
            return Firestore.firestore().collection("Tasks").limit(to: 50)
        }
        
        fileprivate var query: Query? {
            didSet {
                if let listener = listener {
                    listener.remove()
                }
            }
        }
    
    override func viewDidLoad() {
            super.viewDidLoad()
            self.query = baseQuery()
        }
    
     override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            self.listener.remove()
        }

    CloudFirestoreからリアルタイムでデータを読み取る

    ドキュメント参照を配置したら、viewWillAppear(_animated: Bool)で 、前に作成したリスナーをクエリスナップショットの結果に関連付け、ドキュメントのリストを取得します。これは、Firestoreメソッドのquery?.addSnapshotListenerを呼び出すことで実行されます。 :

    self.listener =  query?.addSnapshotListener { (documents, error) in
                guard let snapshot = documents else {
                    print("Error fetching documents results: \(error!)")
                    return
                }
                
                let results = snapshot.documents.map { (document) -> Task in
                    if let task = Task(dictionary: document.data(), id: document.documentID) {
                        return task
                    } else {
                        fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
                    }
                }
                
                self.tasks = results
                self.documents = snapshot.documents
                self.tableView.reloadData()
                
            }

    上記のクロージャは、snapshot.documentsを割り当てます 配列を繰り返しマッピングし、新しいTaskにラップします スナップショット内の各データアイテムのモデルインスタンスオブジェクト。したがって、ほんの数行で、クラウドからすべてのタスクを正常に読み込み、グローバルなtasksに割り当てることができます。 配列。

    結果を表示するには、次のように入力します TableView デリゲートメソッド:

    override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return tasks.count
        }
        
        
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            
            let item = tasks[indexPath.row]
            
            cell.textLabel!.text = item.name
            cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray
            
            return cell
        }

    この段階で、プロジェクトをビルドして実行すると、シミュレーターでリアルタイムに表示されるデータを観察できるはずです。 Firebaseコンソールからデータを追加すると、アプリシミュレーターにすぐに表示されるはずです。

    データの作成、更新、削除

    バックエンドからコンテンツを正常に読み取った後、次にデータを作成、更新、および削除します。次の例では、セルをタップすることによってのみアイテムに完了のマークを付けることができるという工夫された例を使用して、データを更新する方法を示します。 collection.document( item.id ).updateData(["done": !item.done]) クロージャプロパティ。特定のドキュメントIDを参照し、ディクショナリの各フィールドを更新します。

    override func tableView(_ tableView: UITableView,
                                didSelectRowAt indexPath: IndexPath) {
    
            let item = tasks[indexPath.row]
            let collection = Firestore.firestore().collection("Tasks")
    
            collection.document(item.id).updateData([
                "done": !item.done,
                ]) { err in
                    if let err = err {
                        print("Error updating document: \(err)")
                    } else {
                        print("Document successfully updated")
                    }
            }
    
            tableView.reloadRows(at: [indexPath], with: .automatic)
            
        }

    アイテムを削除するには、document( item.id ).delete() 方法:

    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
            return true
        }
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    
            if (editingStyle == .delete){
                let item = tasks[indexPath.row]
                _ = Firestore.firestore().collection("Tasks").document(item.id).delete()
            }
    
        }

    新しいタスクを作成するには、ストーリーボードに新しいボタンを追加し、そのIBActionを接続する必要があります。 ビューコントローラに、addTask(_ sender:)を作成します 方法。ユーザーがボタンを押すと、ユーザーが新しいタスク名を追加できるアラートシートが表示されます:

    collection("Tasks").addDocument
        (data: ["name": textFieldReminder.text ?? 
            "empty task", "done": false])
    
    

    次のように入力して、アプリの最後の部分を完成させます。

    @IBAction func addTask(_ sender: Any) {
            
            let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert)
            
            alertVC.addTextField { (UITextField) in
                
            }
            
            let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil)
            
            alertVC.addAction(cancelAction)
            
            //Alert action closure
            let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in
                
                let textFieldReminder = (alertVC.textFields?.first)! as UITextField
                
                let db = Firestore.firestore()
                var docRef: DocumentReference? = nil
                docRef = db.collection("Tasks").addDocument(data: [
                    "name": textFieldReminder.text ?? "empty task",
                    "done": false
                ]) { err in
                    if let err = err {
                        print("Error adding document: \(err)")
                    } else {
                        print("Document added with ID: \(docRef!.documentID)")
                    }
                }
                
            }
        
            alertVC.addAction(addAction)
            present(alertVC, animated: true, completion: nil)
            
        }

    アプリをもう一度ビルドして実行し、シミュレーターが表示されたら、いくつかのタスクを追加し、いくつかを完了としてマークしてみてください。最後に、いくつかのタスクを削除して削除機能をテストします。 Firebaseデータベースコンソールに切り替えてコレクションとドキュメントを確認することで、保存されているデータがリアルタイムで更新されたことを確認できます。

    フィルタリングと複合クエリ

    これまでのところ、特定のフィルタリング機能を使用せずに、単純なクエリのみを使用してきました。もう少し堅牢なクエリを作成するには、whereFieldを使用して、特定の値でフィルタリングできます。 条項:

    docRef.whereField(“name”, isEqualTo: searchString)

    order(by: )を使用して、クエリデータを並べ替えたり制限したりできます。 およびlimit(to: ) 次のような方法:

    docRef.order(by: "name").limit(5)

    FirebaseDoアプリでは、すでにlimitを使用しています 基本クエリで。上記のスニペットでは、別の機能である複合クエリも使用しました。この機能では、順序と制限の両方がチェーンされています。次の例のように、必要な数のクエリを連鎖させることができます。

    docRef
        .whereField(“name”, isEqualTo: searchString)
    	.whereField(“done”, isEqualTo: false)
    	.order(by: "name")
    	.limit(5)

    1. OracleのSYSTIMESTAMP関数

    2. SQLiteの日付/時刻関数の有効な修飾子

    3. sqliteLog 14:行でファイルを開くことができません

    4. カスタムOracleタイプのオブジェクトマッピングを渡そうとしたときに無効な名前パターン