관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Firestore 연동, 사용 방법 본문

iOS 응용 (swift)

[iOS - swift] Firestore 연동, 사용 방법

jake-kim 2020. 11. 21. 20:46

우선 이곳에서 Firebase연동

Firestore란?

  • 클라우드 DB
  • REST API와 같은 것을 쓰고, 데이터를 gRPC이나 웹 소켓과 같이 stream형태로 받고 싶은 경우 사용
  • DB에 "구독"을 할 수 있는 개념이 있어서, 앱이 DB에 구독을 하고 있을 때 서버에서 DB의 수정이 생기면 자동으로 앱에 데이터를 넘겨주는 시스템 존재

의존성

  • 아래의 3가지 파일은 모두 하나의 프로젝트 또는 하나의 프레임워크에 존재해야함
  • 앱의 구조를 신경쓴다고 해서, 세 가지 프레임 워크가 떨어져 있으면 crash를 발생하고 Firebase에서 내려주는 형태인 GeoPoint형을 사용하지 못하는 버그 존재
pod 'Firebase/Core'
pod 'Firebase/Firestore'
pod 'FirebaseFirestoreSwift'

Firestore에서 DB구조

  • Collection -> Document -> Colection -> Document... 순서로 구성
  • 보통 document에 id를 두고 쿼리할 수 있게끔 구성
  • 위 사진에서 status의 정보에 접근하는 path는 alpha/order/status

Swift에서 Firestore사용하는 구조

  • Protocol 정의: 이 프로토콜을 구현한 클래스들은 Response값(Codable형)과 path, subscribe, removeListener(구독 해제)를 필수로 구현할 수 있게끔
//
//  StreamUseCase.swift
//  Domain
//
//  Created by 김종권 on 2020/11/11.
//

import Foundation
import Firebase

protocol StreamUseCase {
    associatedtype Response

    var path: String { get }

    func subscribe(query: String, completion: @escaping (Result<Response, StreamError>) -> Void)
    func removeListener()
}
  • 프로토콜에 관한 오류 타입 정의
//
//  StreamErrors.swift
//  Domain
//
//  Created by 김종권 on 2020/11/11.
//

import Foundation

public enum StreamError: Error {
    case notFound
    case wrongGettingDocument(Error)
    case decoderError
}
  • StreamUseCase프로토콜을 구현한 Base클래스 정의
//
//  BaseStreamUseCase.swift
//  TapRider
//
//  Created by 김종권 on 2020/11/13.
//

import Foundation
import Firebase
import FirebaseFirestoreSwift

public class BaseStreamUseCase<ResponseType: Codable>: StreamUseCase {
    public typealias Response = ResponseType
    var path: String {
        return "require the path"
    }

    private var listenerObject: ListenerRegistration?
    private let db: Firestore
    public init(db: Firestore) {
        self.db = db
    }

    public func subscribe(query: String, completion: @escaping (Result<Response, StreamError>) -> Void) {

        listenerObject = db.collection(path).document(query)
            .addSnapshotListener { (querySnapshot, error) in

                guard let querySnapshot = querySnapshot else {
                    completion(.failure(.notFound))
                    return
                }

                if let error = error {
                    completion(.failure(.wrongGettingDocument(error)))
                    return
                }

                guard let data = try? querySnapshot.data(as: Response.self) else {
                    completion(.failure(.decoderError))
                    return
                }

                completion(.success(data))
            }
    }

    public func removeListener() {
        listenerObject?.remove()
    }
}
  • Base클래스를 상속받은 실제로 쓰는 입장에서 사용되는 클래스 정의
//
//  VehicleLocationUseCase.swift
//  Domain
//
//  Created by 김종권 on 2020/11/12.
//

import Foundation
import Firebase
import FirebaseFirestoreSwift

final class MyStreamUseCase: BaseStreamUseCase<VehicleLocationResponse> {
    override var path: String {
        return StreamPath.vehicleLocation // struct형 static let으로 따로 stream path에 관한 상수 정의
    }
}
  • 쓰는 입장에서의 코드
let usecase = MyStreamUseCase(db: Firestore.firestore()) // 실제로는 주입하는 형태로 사용할 것
usecase.subscribe(query: "123") { (result) in
    switch result {
    case .success(let response):
        print(response)
    case .failure(let error):
        print(error)
    }
}

// 구독을 종료하고 싶은 deinit()부분에서 구독해제
MyStreamUseCase.removeListener()

 

* Firestore에 관해 더욱 자세한 개념은 이곳 참고

Comments