LoginSignup
8
7

More than 3 years have passed since last update.

【Swift】Firestore × Codable 〜取得データを簡単にデコード〜

Last updated at Posted at 2021-02-10

最近、iOSアプリ開発でCloud Firestoreを使っているのですが、何も気にせず使っていたら1つ気になることが、、、
Firestoreから取得したデータのデコードがめんどくさい
WebAPIからJSONで取ってきたデータは、Codableで簡単にデコードできるので、Firestoreから取ってきたデータも簡単にデコードしたい!と思い調べてみたので、記事にしました。
Codableについては、こちらの記事が大変参考になるかと思います。
https://qiita.com/UJIPOID/items/2c436a80f1167f7bcac0

リファクタリング前のコード

Firestore × Codableを導入する前のコードをこちらに貼っておきます。

import FirebaseFirestore

// MARK: - Menu
struct Menu: Codable {
    let id: String
    let category: String
    let imageName: String
    let name: String
    let price: Int
}

// Firestoreからデータ全件取得&デコード
func fetchMenuData() {
    let db = Firestore.firestore()
    // Firestoreからメニューデータを取得
    db.collection("menu").getDocuments { (_snapShot, _error) in
        if let snapShot = _snapShot {
            let menuList = snapShot.documents.map { menu -> Menu in
                let data = menu.data()
                // デコード開始(ここがスッキリしない)
                let id: String = data["id"] as! String
                let category: String = data["category"] as! String
                let imageName: String = data["imageName"] as! String
                let name: String = data["name"] as! String
                var price = 0
                if let priceData = data["price"] {
                    if let priceInt = Int(String(describing: priceData)) {
                        price = priceInt
                    }
                }
                return MenuDetail(id: id, category: category, imageName: imageName, name: name, price: price)
            }
            // 取得データを出力
            print(menuList)
        } else {
            print("Data Not Found")
        }
    }
}

ざっとこんな感じです。

やろうとしていることとしては、Firestoreに入っているデータを全件取得する処理です。
JSONをCodableを使ってデコードすることに慣れてしまっていると、少し冗長に感じます。
構造体のプロパティ数が増えたらもっと長くなってしまいますね。

というわけで、早速リファクタリングしていきましょう。

FirebaseFirestoreSwift を導入する

■ FirebaseFirestoreSwiftについて
Firestoreから取得したデータのデコードを簡単にやってくれるライブラリです。
https://cocoapods.org/pods/FirebaseFirestoreSwift

とりあえず導入してみる

pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'

これで、pod installを実行すればOKなはずです。

Firestore × Codable で実装

ちなみに、Firestoreからの取得データをクエリで指定して1件のみ取得したい方はこちらの公式リファレンスをご覧ください。(多分こっち見たほうが早いです)
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja
→ 場所分かりづらいですが、ページ検索で「FirebaseFirestoreSwift」を検索すれば出てきます。

リファクタリングした結果


import FirebaseFirestore
import FirebaseFirestoreSwift

--- 省略 --- 

// MARK: - Menu
struct Menu: Codable {
    let id: String
    let category: String
    let imageName: String
    let name: String
    let price: Int
}

// Firestoreからデータ全件取得&デコード
func fetchMenuData() {
    let db = Firestore.firestore()
    // Firestoreからメニューデータを取得
    db.collection("menu").getDocuments { (_snapShot, _error) in
        if let snapShot = _snapShot {
            let documents = snapShot.documents
            let menuList = documents.compactMap {
                // この1行でデコード終了
                return try? $0.data(as: Menu.self)
            }
            // 取得データを出力
            print(menuList)
        } else {
            print("Data Not Found")
        }
    }
}

ポイントは、ここです。

let menuList = documents.compactMap {
    // この1行でデコード終了
    return try? $0.data(as: Menu.self)
}

リファクタリング前のコードと比較すると、明らかに簡単にデコードできていることがお分かりいただけると思います。
リファクタリング前は、data["KEY"]の形式で、値を1件ずつ取り出していましたが、「FirebaseFirestoreSwift」を使うことで、その必要がなくなりました。

余談ですが、今回は複数件取得したデータをデコードする際にcompactMap(_:)を使いました。
try? $0.data(as: Menu.self)が失敗した場合は、nilが帰ってきますので、その場合は特にエラーハンドリングをせずに、配列に含めないという操作を1行で簡単に実装できます。

compactMap(_:)についてはこちら。
https://qiita.com/yum_fishing/items/7592a56f7e9dbeef8e75

ちなみに公式リファレンスのやり方をほぼ参考にしていますが、デコードに失敗した場合に、処理を行いたい場合はこのようにすれば良いと思います。
エラー処理適当です🙏

let menuList = documents.compactMap { doc in

    let result = Result {
        try doc.data(as: Menu.self)
    }

    switch result {
    case .success(let _menu):
        if let menu = _menu {
            return menu
        } else {
            print("Document does not exist")
            return nil
        }
    case .failure(let error):
        print("Error decoding menu: \(error.localizedDescription)")
        return nil
    }
}

参考にさせていただいた記事・サイト

https://qiita.com/tsubasa_hiroe/items/43b3552975d794383881
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7