iOS

[firebase] firestore에서 받은 데이터 쉽게 decode하기

jaemjung 2022. 3. 27. 21:04

문제상황 



현재 iOS 동아리에서 간단한 출석체크 앱을 만들고 있는데, firebase의 firestore를 앱의 원격 저장소로 사용중이다.

firestore에는 데이터가 key-value 형태로 저장되며, 이를 앱에서 호출하면 NSDictionary 형태로 불러오게 된다. 

 

우리 팀이 제작을 맡은 출석 인원을 체크하는 뷰에서는 다음과 같은 간단한 구조체로 이루어진 Model과 이 구조체의 배열을 Publish하는 ViewModel을 참조하며 뷰를 그려주고 있다.

 

//Model
struct Member: Identifiable, Codable {
    
    var id: String
    var intraID: String
    var dates: [Date]
    
}

//ViewModel

struct AttendedMemberViewModel: ObservableObject {
	//...
    @Published private(set) var members: [Member]
    
    //...
}

 

보통 서버에서 key-value 형식의 데이터를 불러올 때에는 json 형식을 가진 string으로 가져오게 된다.

swift에서는 이를 decode해서 저장해줄 구조체가 Codable 프로토콜을 준수하기만 한다면 손쉽게 json을 이용하여 객체를 생성해 줄 수 있다.

 

그런데 문제는, 이놈의 Firestore가 데이터를 무조건 NSDictionary로 뱉어낸다는 것...

 

NSDictionary로 나온 값을 다시 json형식으로 바꾼 후 Codable의 기능을 이용하여 decode 해주는 것도 이상하다고 생각하여 그냥 호출 결과로 나온 NSDictionary를 순회하며 decode 해주도록 코드를 짰는데 너무 복잡하고 꼴보기가 싫었다. 분명히 더 간단한 방법이 있을 것이라 생각하여 이를 리팩토링 하기로 했다.

 

어떻게 해결했나?

 

먼저 아래의 링크를 참고하여 해결할 수 있었다.

https://peterfriese.dev/posts/firestore-codable-the-comprehensive-guide/

 

Mapping Firestore Data in Swift - The Comprehensive Guide | Peter Friese

Enjoyed reading this article? Subscribe to my newsletter to receive regular updates, curated links about Swift, SwiftUI, Combine, Firebase, and - of course - some fun stuff 🎈

peterfriese.dev

 

생각보다 너무 간단하게도 Firestore 자체에서 이미 불러온 데이터를 이용해 Codable 프로토콜을 준수하는 구조체로 자동 생성해주는 기능을 제공하고 있었다...

 

먼저 해당 기능을 사용하고자 하는 부분에서 FirebaseFirestoreSwift를 import 해주어야 한다.

 

import Foundation
import Firebase
import FirebaseFirestore
import FirebaseFirestoreSwift //요거까지 import하는게 중요...

 

그리고 getDocument().data()를 이용하여 데이터를 가져올 때, as와 함께 생성하고자 하는 구조체를 넣어주기만 하면 된다.

 

struct FireBaseService {
 
    private let db = Firestore.firestore()
    /...
    
    func fetchMembers() async throws -> [Member] {
        let snapshot = try await db.collection("testCollection").getDocuments()
        return snapshot.documents.compactMap { document in
            try? document.data(as: Member.self) // 이렇게 간단하게...
        }
    }
    
    /....
}

 

단, 이를 사용하기 위해서는 Member 구조체를 살짝 수정해줘야 했다. 먼저 id 변수의 경우 @DocumentID라는 PropertyWrapper를 추가해줘야 하며, 옵셔널 타입의 변수로 바꿔줘야 한다. 또한 firestore에 멤버들이 출석한 날짜가 [String: Timestamp] 형태의 dictionary 형태로 저장되어있어, 이를 반영하여 date 역시 살짝 수정해주었다.

 

struct Member: Identifiable, Codable {
    
    @DocumentID var id: String?
    var intraID: String
    var dates: [String: Date]
    
}

 

꽤나 오래 고심한 문제였는데, 생각보다 너무 쉽게 풀려서 황당했다...