Firebaseから取得したドキュメントをカスタムクラスに入れていく(やってみる)

はじめに

Firestoreから取得したドキュメントをカスタムクラスに入れていくときに紹介したflatMapでスマートに進める話の続きです。Google本家のサンプルコードでは1つのドキュメントを処理する例だけしか見つけられませんでした。

複数のドキュメントを取ってきたときはどうするのか?試行錯誤をしてみました。

元のコード

今回いじるコードはこれです。

docRef.getDocuments() { (querySnapshot, err) in
  if let err = err {
    print("Error: \(err)")
  } else {
    for document in querySnapshot!.documents {
      var customClass = CustomClass()
      customClass.name = document.data()["name"] as! String
      customClass.address = document.data()["address"] as! Int
      self.data.append(customClass)//カスタムクラスを配列に追加していっています
    }
  }
}

このコード、forループが頂けないです。
何が頂けないって、文字列(“name”や”address”)が居る点と、as! Stringのようなキャスト(的なこと)を行っている点です。逐一カスタムクラスの型やプロパティを確認する必要がありますよね…… 下手をすればセツケイシヨという名の古文書を紐解くことになります。

スッキリさせたいのと、できればカスタムクラス(CustomClass)に取得したデータをそのまま渡して初期化させたいです。

カスタムクラスにデータの塊を渡して初期化させる

カスタムクラスのプロパティに1つずつ値を割り当てていく作業、これは面倒。
この方法だと、DBに保存する項目が変わる度に割り当てる作業を追加したり削除したりしないといけません。これはミスの元。

Firestore側から渡されたデータの処理は極力1か所にまとめておきましょう。
というわけで、カスタムクラス内に収めてしまいます。

こうすることで、Firestore側から渡されたデータの中身がどうであれ、それをどう処理するかはカスタムクラスの中だけで完結します。

struct CustomClass: {    
    var name: String = ""
    var address: String = ""
    init(_ dictionary: [String: Any]) {
        self.name = dictionary["name"] as! String
        self.address = dictionary["address"] as! String
    }
    func getArray() -> [String: Any] {
        return [
            "name": self.name,
            "address": self.address
        ]
    }
}

ついでと言ってはなんですが、Firestoreへ保存するときのドキュメントを作るメソッドも追加しておきました。名称getArrayは皆様のセンスで何とかしてください。

mapを使ってスッキリさせる

ごめんなさい。flatmapは使いこなしきれませんでした。といいますか、今回はmapで良いような気がしました。

querySnapshot!.documentsでドキュメントを1つずつ取り出して、カスタムクラスに納めていくという処理なので、結局こうなります。先ほどのカスタムクラスの初期化も早速使いました。

self.data = querySnapshot!.documents.map { CustomClass($0.data()) }

ねっ?簡単でしょ?というノリですがこれで終了です。5〜6行がスッキリ1行になりました。また、プロパティ名やそれぞれの型が何かを意識しなくても良くなりました。

ちなみに、mapの代わりにcompactMapも使えます。ただし、querySnapshot!.documentsの中にnilが混じっていたり、違う型が入り混じっているとは考えにくいので、mapで十分かと思います。
また、!でアンラップしているのが気持ち悪いという方はif letguardでほどいてあげると幸せになれると思います。