[iOS] App Development

[iOS] 다이어리 데이터 FireStore CRUD 방법 / 모델 구조 / 실시간 업데이트하는 법 / Timestamp to NSDate 타입변경

ddgoori 2021. 12. 23. 04:05

FireStore와 다이어리 iOS App을 연동하며 드는 의문점들과 솔루션

 

다이어리 데이터 FireStore CRUD 방법
모델 구조
실시간 업데이트하는 법
Timestamp to NSDate 타입변경

 

 

1) FireStore에 어떤 모델 구조로 저장해야하는지와 데이터 추가 방법

 

FireStore Datamodel 

Collection - 폴더(Users)

Document - 문서(Dahae)

Data - 문서안의 내용(name, bornDate, address...)

 

Each Individual Data를 만들어줌

name, sender, content of message, timestamp

Dictionary와 Json을 사용할 수 있다.

 

아래와 같이 Diary 모델 구조를 짜보았다.

//
//  Diary.swift
//  cocoBaby_2021
//
//  Created by DaHae Kim on 2021/11/17.
//

import Foundation
import FirebaseFirestore

protocol DocumentSerializable {
    init?(dictionary:[String:Any])
}

struct Diary {
    var content: String
    var timeStamp: Date
    var emoji: String
    
    // Key(Strin(g)-Value(Any)
    var dictionary: [String:Any] {
        return [
            "content": content,
            "timeStamp": timeStamp,
            "emoji": emoji
        ]
    }
}

extension Diary : DocumentSerializable {
    init?(dictionary: [String : Any]) {
        guard let content = dictionary["content"] as? String,
              let timeStamp = dictionary["timeStamp"] as? Date,
              let emoji = dictionary["emoji"] as? String else { return nil }
       
        self.init(content: content, timeStamp: timeStamp, emoji: emoji)
    }
}

 

키값은 timeStamp로 사용해도 되겠는데..?(밑에 더 읽어보면 보면 파이어스토어에서 자동생성해주는 ID로 정함)

정리하면 모델 구조는 이런식이다 Diaries 컬렉션으로 들어가서 자신의 UID값으로 다이어리를 조회해 온다. 그 이후에 Diary덩어리로 여러개가 쌓여야 한다는 것이지..

 

FireStore의 sub-collection으로 데이터 계층화할 수 있음 -> 여태까지 적용해온 데이터 모델과는 다름!

컬렉션 -> 문서 -> 컬렉션 -> 문서 이런식으로 구현해야함.

 

위와 같은 구조로 아래 소스코드처럼 [ Diaries -> UID -> Diary -> 데아터 ]방식으로 구현했다.

자신의 UID로 들어간 이후 문서ID는 난수로 자동생성된다. 자동으로 생성되는 문서 ID값을 키값으로 두면 될 것 같다.

 

  let newDiary = Diary(content: note, timeStamp: Date(), emoji: "")
            
  self.ref = self.db
                .collection("Diaries").document(self.userUid!)
                .collection("Diary").addDocument(data: newDiary.dictionary)

 

위 소스코드를 실행하면 FireStore에 Diaries 컬렉션으로 UID(검정색박스로 가린값) 문서들이 생기고 

그 문서안에서 컬렉션을 Diary로 만들고, 다이어리 데이터를 저장한다.

 

Diary 컬렉션을 누르면 아래와 같이 데이터가 저장된 것을 확인할 수 있다.

오케이 굳..( '-')b

 

 

 

2) FireStore에 저장된 다이어리 데이터 가져오기

 

그렇다면 저장된 데이터를 어떻게 불러오고, 불러온 데이터를 테이블 뷰에 차곡차곡 뿌려줄 수 있을까?

[모든Diaries -> 나의UID -> Diary -> 데이터] 에서 Diary가 가진 모든 데이터 개별 다이어리들을 먼저 가져와야한다.

즉, we need to get all the documents from Diary

 

a. Get data from firestore

b. Store received data to local array => 여기서 flatMap을 사용하여 우리가 원하는 데이터 모델(위에서 설정해놓은 Diary Struct대로)로 데이터를 받아올 수 있다. (flatMap을 잘 모르고 사용하다보니 오류가나서 그냥 for in으로 Array에 append해주는 방법을 사용했다. 다음에 해보는 것으로 ㅠ..(이렇게 기술부채가!!!!!))

 

아래와 같이 구현했다. 

   func loadDiaryData() {
        db.collection("Diaries").document(userUid!)
            .collection("Diary").getDocuments() { (querySnapshot, err) in
                if let err = err {
                    print("Error getting documents: \(err)")
                } else {
                    for document in querySnapshot!.documents {
                        print("\(document.documentID) => \(document.data())")
                        if let stamp = document.data()["timeStamp"] as? Timestamp {
                            self.diaryArray.append(Diary(content: document.data()["content"] as! String, timeStamp: stamp.dateValue() as! Date, emoji: document.data()["emoji"] as! String))
                        }
                    }
                    DispatchQueue.main.async {
                        self.tableView.reloadData()
                    }
                }
            }
        
    }

 

* 중간에 났던 오류

Could not cast value of type 'FIRTimestamp' (0x1e4483570) to 'NSDate'

=> FireStore의 TimeStamp가 Swift의 Date타입과 다르기 때문에 캐스팅할 수 없다는 오류가 나타난다. 

 

TimeStamp를 NSDate으로 변경해줘야 한다. 둘은 엄연히 다른 데이터 타입임!

아래방법으로 TimeStamp데이터 타입으로 먼저 받아와준 뒤에 Swift5부터 생긴 것으로 보이는(?) dateValue를 해주면 Date()타입으로 변경해줄 수 있다.

import FirebaseFirestore
init?(document: QueryDocumentSnapshot) {
        let data = document.data()
       guard let stamp = data["timeStamp"] as? Timestamp else {
            return nil
        }
       let date = stamp.dateValue()
}

 

 

 

3) 다이어리에 데이터 추가한 후 테이블뷰에 바로 반영되어 나타나게 하기

 

FireStore가 리얼타임 데이터베이스이기 때문에 실시간 업데이트를 listen하게 할 수 있음

It's kind of a listener. It listens app for whole time while app is running and until we terminate the app.

 

조건(데이터를 불러올 때 조건쿼리를 쓸 수 있다.)

- whereField를 사용하여, 우리가 업데이트하는 timeStamp가 현재 Date보다 많을때만 쿼리한다.

- 컬렉션의 변화를 알아차리고 유저인터페이스에 알려주는 역할이 addSnapshotLister { querySnapshop~

- documentChange.forEatch를 통해 업뎃됐을때, 삭제 됐을때 등의 경우에서 어떤 실행을 할지 정할 수 있다.

- 오직 달라진 부분만 업뎃한다. 

 

    func CheckForUpdate() {
        db.collection("Diaries").document(self.userUid!)
            .collection("Diary").whereField("timeStamp", isGreaterThan: Date())
            .addSnapshotListener {
                querySnapshopt, error in
                print("CheckForUpdate Date: \(Date())")
                guard let snapshot = querySnapshopt else { return }
                
                snapshot.documentChanges.forEach {
                    diff in
                    
                    if diff.type == .added {
                        if let stamp = diff.document.data()["timeStamp"] as? Timestamp {
                            self.diaryArray.append(Diary(content: diff.document.data()["content"] as! String, timeStamp: stamp.dateValue() as! Date, emoji: diff.document.data()["emoji"] as! String))
                        }
                        DispatchQueue.main.async {
                            self.tableView.reloadData()
                        }
                    }
                }
            }
    }

 

+ 추가지식

=> FireStore 인터넷이 없어도 기존에 받아온 데이터는 볼 수 있다. 그리고 인터넷이 다시 연결되면 파이어스토어가 자동으로 데이터를 fetch해준다.

 

 

결과 화면

 

실제 FireStore에 저장된 데이터

 

참고로 아래 뷰에서 날짜 데이터는 UILabel 디폴트값을 보여주고 실제 TimeStamp로 넣어주는 것은 안한 상태다.

FireStore에서 조회해온 데이터와 추가시 반영된 데이터

 

 

 

 

*출처: FireStore 공식문서

 

Cloud Firestore 데이터 모델  |  Firebase Documentation

의견 보내기 Cloud Firestore 데이터 모델 Cloud Firestore는 NoSQL 문서 중심의 데이터베이스입니다. SQL 데이터베이스와 달리 테이블이나 행이 없으며, 컬렉션으로 정리되는 문서에 데이터를 저장합니다.

firebase.google.com