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