Firebase FireStore使ってみた

###対象
これからFireStoreを使ってみようと思っているiOSエンジニア

##FireStoreとは
Firebaseが提供しているデータベースです。
MySQLなどのRDB（リレーショナルデータベース）の行列とは違い、「コレクション」「ドキュメント」「データ」の３つで構成されています。

![structure-data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246668/91762533-48e2-4f7f-1d4d-fa8bbcfee64e.png)

また、アプリとの直接的なやりとりでDBのデータを直接取得できます。

##料金
| 無料枠| | 
|:-:|:-:|
| 保存量|1GB|
| 通信量|10GB/月
| 書き込み量|2/日
| 読み取り量|5/日
| 削除量|2/日|

| 有料| | 
|:-:|:-:|
| 保存量1GBあたり|$0.18|
| 通信量|ロケーションにより異なる
| 書き込み量10万件あたり|$0.18
| 読み取り量10万件あたり|$0.06
| 削除量10万件あたり|$0.02|

Firebaseから出ているDBには、もう1つ「Realtime Database」もあります。
こちらとの機能的な違いもありますが、料金体系も違います。
「Realtime Database」は保存量などに課金されますが、「FireStore」は書き込み/読み取り回数に課金されます。

##Firebaeプロジェクトをアプリへ追加する
[こちら](https://qiita.com/kumas/items/74d74a87b04d8614259a)を参考にアプリへFirebaeプロジェクトを追加してください。

##インストール（アプリへ追加）
Podファイルへの記載

```Podfile.

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

```

インストール

```ターミナル.
Xcodeプロジェクトのあるディレクトリ $ pod install
```

アプリ内で初期化

```AppDelegate.swift
import Firebase

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()

return true
}
```

以上で、アプリ内で、FireStoreが使えるようになります。

##データの追加
```Firestore.swift
// FIRFirestoreインスタンスの作成
    let db = Firestore.firestore()
// 「users」コレクションの作成（ドキュメント名指定）
    func creatUserCollectionDesignationDocument() {
        // "users"という名称のコレクションを作成
        // "hoge"という名称のドキュメントを作成
        // ["name": "hoge"]というデータを保存
        db.collection("users").document("hoge").setData(["name": "hoge"]) { error in
            if let error = error {
                print("エラーが起きました")
            }
            else {
                print("ドキュメント「hoge」が保存できました")
            }
        }
    }
// usersコレクションの作成（ドキュメント名自動）
    func creatUserCollectionAutomaticDocument() {
        // "users"という名称のコレクションを作成
        // ドキュメント名を自動でを作成
        // ["name": "hoge"]というデータを保存
        db.collection("users"). addDocument(["name": "hoge"]) { error in
            if let error = error {
                print("エラーが起きました")
            }
            else {
                print("ドキュメントが保存できました")
            }
        }
    }
```

同名のドキュメントがあった場合は、上書きされてしまいます。
同名ドキュメントがあった場合は、データを加算させる
無かった場合は、新規で追加したい場合は、「merge: true」をつける

```Firestore.swift
// 「users」コレクションの作成（ドキュメント名指定）
    func creatDocumentSaveOrUpdate() {
        // "hoge"という名称のドキュメント に対して
        // ["age": "20"]というデータを保存
        db.collection("users").document("hoge").setData(["age": 20], merge: true) { error in
            if let error = error {
                print("エラーが起きました")
            } 
            else {
                print("ドキュメント「hoge」が保存できました")
            }
        }
    }

```

Document：hogeがあったら、
["name": "hoge",
"age": 20]

無かったら、
["age": "20"]

と、なります。

##データの削除
```Firestore.swift
// FIRFirestoreインスタンスの作成
    let db = Firestore.firestore()
// usersコレクション内のhogeドキュメントの削除
    func deleteDocument() {
        db.collection("users").document("hoge").delete() { err in
            if let err = err {
                print("Error removing document: \(err)")
            } 
            else {
                print("Document successfully removed!")
            }
        }
    }
// hogeドキュメント内の「"name": "hoge"」の削除
    func deleteDocument() {
        db.collection("users").document("hoge").updateData([
            "name": FieldValue.delete(),
        ]) { err in
            if let err = err {
                print("Error updating document: \(err)")
            } 
            else {
                print("Document successfully updated")
            }
        }
    }
```

コレクションを削除したい場合は、コレクション内のドキュメントを全て削除する必要があります。

##データの検索
```Firestore.swift
// FIRFirestoreインスタンスの作成
  let db = Firestore.firestore()
// コレクション「user」内の全ドキュメント取得
  func fetchDocument() {
    db.collection("user").getDocuments() { (querySnapshot, err) in
        if let err = err {
          print("Error getting documents: \(err)")
        }
         else {
            for document in querySnapshot!.documents {
                print("\(document.documentID) => \(document.data())")
            }
        }
     }
  }
// 特定のドキュメントを指定してそのドキュメント内のデータ全てを取得
  func fetchDocument() {
     db.collection("user").document("hoge").getDocument { (document, error) in
        if let document = document, document.exists {
            let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
            print("Document data: \(dataDescription)")
        } 
        else {
            print("Document does not exist")
        }
     }
  }

```
###スナップショップ・リスナー（自動同期）
検索に「addSnapshotListener」を付与します。

```Firestore.swift
// FIRFirestoreインスタンスの作成
  let db = Firestore.firestore()
// ドキュメント「hoge」に対して、取得とスナップショップを設定
  func fetchDocumentSnapshotListener() {
   　 db.collection("user").document("hoge").addSnapshotListener { (querySnapshot, err) in
        　if let err = err {
         　  print("Error getting documents: \(err)")
       　 } 
          else {
            for document in querySnapshot!.documents {
                print("\(document.documentID) => \(document.data())")
            }
        　}
    　}
  }
```

###条件
```  Firestore.swift
  // FIRFirestoreインスタンスの作成
    let db = Firestore.firestore()
  // コレクション「user」に対して、条件(nameがhoge)に一致したドキュメントを取得する
    func fetchDocumentSnapshotListener() {
      db.collection("user").whereField("name", isEqualTo: "hoge").getDocument() { (querySnapshot, err) in
           if let err = err {
              print("Error getting documents: \(err)")
           } 
           else {
              for document in querySnapshot!.documents {
                  print("\(document.documentID) => \(document.data())")
              }
           }
       }
    }
    // コレクション「user」に対して、複数条件(nameがhoge　かつ　ageが18以上)に一致したドキュメントを取得する
      func fetchDocumentSnapshotListener() {
        db.collection("user").whereField("name", isEqualTo: "hoge").
                              whereField("age", isGreaterThanOrEqualTo: 18).getDocument() { (querySnapshot, err) in
             if let err = err {
                print("Error getting documents: \(err)")
             } 
             else {
                for document in querySnapshot!.documents {
                    print("\(document.documentID) => \(document.data())")
                }
             }
         }
      }
```

Swiftの場合、比較演算子はメソッド名になっています。

| 演算子|メソッド名|
|:-----:|:-------:|
| ==   | isEqualTo |
| <    | isLessThan | 
| >    | isGreaterThan |      
| <=   |  isLessThanOrEqualTo |     
| >=   | isGreaterThanOrEqualTo |  
| 配列内に指定した要素があったら   | arrayContains |  

足らないこと、間違い等ありましたら、コメントもしくは、[@gurensouen](https://twitter.com/gurensouen)
にご連絡いただけますと幸いです。