Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@lemonade_dot_log

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

最近、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

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
lemonade_dot_log
新卒でSEとしてDBやSQL周りの仕事をした後、iOSエンジニアとして転職しました。自分が勉強したことを中心に投稿していきます。
engineerlife
技術力をベースに人生を謳歌する人たちのコミュニティです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?