포스트

FlashChat (7)

Firestore 사용해보기

Firestore에 대한 Docs이다 참고하자.

Database 구축하기

Firebase의 Console로 들어가서 Firestore Database를 선택해주자.

Realtime DB는 이전에 있었던것이라고 한다.

DB를 만들어 보자.

만들기를 누르면 다음과 같이 지역설정이 뜨는데 us-central로 해주자

(난 모르고 Seoul을 했다. 어차피 테스트용도로 크게 상관은 없다.)

그러면 모드 설정이 나오는데,

프로덕션모드를 하면 타사에서 읽기 쓰기가 거부된다. 즉 우리가 테스트를 하기가 너무 어려워진다.

테스트 모드는 한달동안 자유롭게 쓸 수 있다.

테스트모드로 하고 만들어주자.

그다음 다시 xcode로 돌아와서 Cloud Firestore를 초기화 해줘야한다.

설명은 Docs에 있다.

1
2
3
4
5
6
7
8
9
10
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
    FirebaseApp.configure()
        
    let db = Firestore.firestore()
        
    print(db)
        
    return true
}

db가 잘 나오는지 print를 넣어주었다.

잘나온다.

값이 안나온다면? Terminal에서 pod --version을 입력하여 버젼을 확인해보고 업데이트를 해줘야한다.

그리고 다시 설치를 해봐야한다. sudo gem install cocoapods

이게 프린트가 안되면 나중에 App Crash가 발생하기 때문이다.

보내기 버튼 구현하기 (DB에 데이터 전달)

1
2
3
4
5
    @IBAction func sendPressed(_ sender: UIButton) {
        
        let messageBody = messageTextfield.text
        
    }

메세지의 내용은 textField의 값으로 가져온다.

그리고 누가 보냈는지를 알기 위해 즉 아래에 있는 sender를 알기 위해서 다시 Firebase Docs로 돌아가자.

1
2
3
4
5
struct Message {
    let sender : String // email 형식으로
    let body : String // 유져가 보내는 Message 내용
}

다음과 같이 옵셔널 바인딩도 해주었다.

1
2
3
4
5
6
7
@IBAction func sendPressed(_ sender: UIButton) {
        
        if let messageBody = messageTextfield.text, let sender = Auth.auth().currentUser?.email {
            
        }
        
    }

그런데 아직 내용은 적지 않았다, 왜냐 저부분은 이제 Database에 접근을 해야하기 때문에 위에 상수로 하나 db 오브젝트를 만들어 준다.

let db = Firestore.firestore()

이때 Firestore를 찾지못한다고 하면 import FirebaseFirestore 를 적어 import 해주자.

이제 내용을 적어주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
@IBAction func sendPressed(_ sender: UIButton) {
        
        if let messageBody = messageTextfield.text, let messageSender = Auth.auth().currentUser?.email {
            db.collection(K.FStore.collectionName).addDocument(data: [K.FStore.senderField : messageSender, K.FStore.bodyField : messageBody]) { (error) in
                if let e = error {
                    print("There was an issue saving dat to firestore. \(e)")
                } else {
                    print("Successfully saved data")
                }
            }
        }
        
    }

우선 문자열로 된 내용들은 전부 상수화 시켜서 오타가 나지않게 하는건 이미 그전에 언급했으므로 제외하고,

collection안에는 우리가 이름을 정해서 넣게 되는데 “messages”를 적었다.

그리고 data는 보낸사람과 내용을 담기위해 Dictionary형식을 사용하였고,

[K.FStore.senderField : messageSender, K.FStore.bodyField : messageBody] 이건

[“sender” : messageSender, “body” : messageBody] 이것을 상수화 해서 적은것이다.

그리고 뒤에 에러가 발생하였을 경우 출력하기 위해 Closure를 사용했다.

작동해보자.

그런데 입력해보려고하니…

이렇게 짤려버린다. 이건 이따가 다시 보완하기로 하고 작동 테스트를 먼저 해보자.

전달이 잘 되었다.

그리고 DB를 확인해보면?

여기도 등록이 잘 된것을 확인할 수 있다.

DB 받아오기

DB를 받아오려면 앱이 실행될때 받아와야 하므로 viewDidLoad에 함수를 호출하는 식으로 하면 된다.

우선 안에 loadMessages() 를 적어주고 그 다음 함수를 구현하는 코드를 작성해보자.

여러 클래스에 대한 타입은 Docs에 있으니 참고하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func loadMessages() {
        messages = []
        
        db.collection(K.FStore.collectionName).getDocuments { (querySnapshot, error) in
            if let e = error {
                print("There was an issue retrieving data from Firestore. \(e)")
            } else {
                if let snapshotDocuments = querySnapshot?.documents {
                    for doc in snapshotDocuments {
                        print(doc.data())
                    }
                }
            }
        }
    }

배열을 초기화 해주었다. 이제는 우리가 작성된 값을 테스트를 하는것이 아니기 때문이다.

Documents에 우리가 가져와야할 DB가 있는데, 그곳에 접근해서 데이터를 가져오는 것이다.

querySnapshot과 error 모두 옵셔널 타입이므로 옵셔널 바인딩을 해준다.

에러가 발생하면 에러메세지를 출력하고, 그렇지 않은경우 데이터를 가져오게 한다.

해당 내용을 출력해보면 아래와 같이 나온다.

값을 잘 받아오고 있다.

여기서 우리가 메세지를 한번 더 입력해보자.

역시나 DB서버와의 통신이 잘 되는걸 알 수 있다.

이제 저 doc을 더 세분화 해서 나눠 보자.

1
2
3
let data = doc.data()
    if let sender = data[K.FStore.senderField] as? String, let body = data[K.FStore.bodyField] as? String {
  }

이렇게 했는데 보니까 옵셔널 바인딩과 다운캐스팅을 다 해주었다 왜냐하면

Data Type이 Any? 이기 때문이다. 그래서 우리가 쓰는 타입에 맞게 다운 캐스팅을 해주고 옵셔널 바인딩을 해준것이다.

함수를 이렇게 작성해주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func loadMessages() {
    messages = []
        
    db.collection(K.FStore.collectionName).getDocuments { (querySnapshot, error) in
        if let e = error {
            print("There was an issue retrieving data from Firestore. \(e)")
        } else {
                if let snapshotDocuments = querySnapshot?.documents {
                    for doc in snapshotDocuments {
                        let data = doc.data()
                        if let messageSender = data[K.FStore.senderField] as? String, let messageBody = data[K.FStore.bodyField] as? String {
                            let newMessage = Message(sender: messageSender, body: messageBody)
                            self.messages.append(newMessage)
                        }
                    }
                }
            }
        }
    }

우리가 받아오는 값을 새로운 오브젝트에 집어넣고 그걸 배열에 다시 넣는 것이다.

작동이 되는지 확인해보자.

분명히 배열에 집어넣었는데 보이지 않는다?

self.tableView.reloadData() 이걸 입력 하지 않아서 그렀다.

말그대로 tableView를 refresh 해주는 것이다.

그리고 이럴때 자주 사용하면 좋은게 우리가 뭔가 UI가 바뀔때 (여기선 TableView를 갱신할때)

그리고 이렇게 클로저 내부에서 작업을 할때 우리가 이전에 사용했던 DispatchQueue를 통해서 비동기 작업을 해주는 것이 더 좋다.

다시 작동확인을 해보자.

데이터를 잘 받아온다.

하지만 아직 메세지를 보내면 업데이트는 안된다.

DB 자동 갱신

현재 우리가 사용중인 getDocuments는 수동적이다. 무슨말이냐 하면

우리가 호출하기전에는 해당 메서드가 자동으로 트리거 되지 않는다는 것이다.

관련 내용이 있는 Docs이다.

해당 내용을 참고하여 코드를 수정한다.

getDocuments → addSnapshotListener 로 바꿔주자.

그리고 실행해보자

업로드는 되는데, 기존에 내역이 그대로 유지된 채로 추가가 되어버린다.

즉 화면 전체가 리셋이 되고 반영이 되지 않고있다.

문제 해결

배열 초기화의 위치만 바꿔 주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 
func loadMessages() {
        
    // messages = [] before

    db.collection(K.FStore.collectionName).addSnapshotListener { (querySnapshot, error) in
            
        self.messages = [] // new
            
        if let e = error {
            print("There was an issue retrieving data from Firestore. \(e)")
        } else {
                if let snapshotDocuments = querySnapshot?.documents {
                    for doc in snapshotDocuments {
                        let data = doc.data()
                        if let messageSender = data[K.FStore.senderField] as? String, let messageBody = data[K.FStore.bodyField] as? String {
                            let newMessage = Message(sender: messageSender, body: messageBody)
                            self.messages.append(newMessage)
                            
                            DispatchQueue.main.async {
                                self.tableView.reloadData()
                            }
                        }
                    }
                }
            }
        }
    }

다시 작동해보면?

잘된다!

가 아니다.

뒤에 추가한 것을 보면 갑자기 중간에 내용이 들어가버린다..?

Firebase사이트로 들어가서 DB를 한번 확인해보자.

왜 마지막에 입력한 굿이 중간에 있는걸까?

바로 messages의 내용을 담는 Docs가 오름차순으로 되어있다.

git으로 비유하면 commit의 번호가 오름차순으로 정렬된셈이다.

DB의 Data 정렬하기.

현재 DB의 정보를 보면 언제 입력했는지에 대한 날짜, 시간 정보가 없다.

이부분을 추가해주어 시간순으로 정렬을 하면 될 것 같다.

다시 코드로 돌아가자.

우리가 메세지를 보낼때 시간에 대한 값이 들어가야하므로. sendPressed로 가서 내용을 수정 해주면 된다.

시간을 아는 방법은 Date 개체를 이용하는 것이다.

초기화를 해주자 ` K.FStore.dateField : Date().timeIntervalSince1970`로 작성한다.

개발자들이 많이 사용하는 방법이 바로 위의 timeIntervalSince1970을 사용하는 것이다.

timeIntervalSince1970 은 1970년 1월 1일 이후의 시간을 알려준다.

이제 시간을 새로 추가해주었으니 기존 메세지들은 DB로 돌아가서 다 지워준다.

그리고 다시 실행시켜서 테스트용 값을 아무거나 입력하고 보내고 DB를 다시 확인해보자.

시간 값이 추가가 되었다.

이젠 정렬을 하면 되겠다.

정렬에 관한 내용은 Docs 를 참고하면 된다.

db.collection(K.FStore.collectionName).order(by: K.FStore.dateField).addSnapshotListener { (querySnapshot, error) in 이렇게 order를 가운데에 추가해주었다.

위에 처럼 너무 코드가 가로로 길어지면? . 앞에서 엔터를 쳐서 끊어주면 가독성이 더 좋아진다.

1
2
3
db.collection(K.FStore.collectionName)
    .order(by: K.FStore.dateField)
    .addSnapshotListener { (querySnapshot, error) in

작동 확인을 해보면?

굿!

DB 보안설정

현재는 누구나 사용가능하지만, 이젠 인증이 된사람만 사용하게 바꾸자.

관련 내용 역시 Docs 를 참고하자.

그리고 그에 맞게 바꿔주면?

보안설정도 끝.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.