Skip to content

Firebase

WooSeok Suh edited this page Aug 2, 2021 · 12 revisions

2021-07-22

by Seok

Step.1

Firebase관리 객체 생성

  • 첫 로그인 유저(!snapshot.exists())의 경우 default values 넣어주는 메서드 추가
  • 화면에 보여줄 정보 firebase에서 가져오는 메서드 추가
import Foundation
import RxSwift
import Firebase

final class DatabaseManager: DatabaseManagerType {
    
    private var ref: DatabaseReference
    let data = [Unit]()
    
    init(_ ref: DatabaseReference) {
        self.ref = ref
    }
    
    func initializeDatabase(_ uuid: String) {
        ref.child("users").child(uuid).getData { [unowned self] error, snapshot in
            guard error == nil else { return }
            
            if !snapshot.exists() {
                let jsonString = FirebaseDataManager.transformToString(uuid)
                self.ref.child("users").child(uuid).setValue(["units": jsonString])
            }
        }
    }
    
    @discardableResult
    func getFirebaseData(_ uuid: String) -> Observable<[Unit]> {
        Observable.create { [unowned self] observer in
            self.ref.child("users").child(uuid).getData { error, snapshot in
                if let error = error {
                    observer.onError(error)
                }
                
                if let data = snapshot.value as? [String: Any] {
                    observer.onNext(FirebaseDataManager.transformToStruct(data))
                }
            }
            return Disposables.create()
        }
    }
}

Firebase와 통신하기 위한 Data 형식을 바꿔주는 객체 생성

  • Firebase에 객체 저장을 위해 특정형태로(NSString, NSDictionary ...) 변경필요
  • Firebase에서 가져온 데이터를 필요한 객체(Unit)으로 변경필요
import Foundation
import Firebase

struct Units: Decodable {
    let units: String
}

final class FirebaseDataManager {
    
    static func transformToStruct(_ data: [String: Any]) -> [Unit] {
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: data, options: [])
            let units = try JSONDecoder().decode(Units.self, from: jsonData)
            let unit = try JSONDecoder().decode([Unit].self, from: Data(units.units.utf8))
            return unit
        } catch let error {
            print(error)
        }
        
        return []
    }
    
    static func transformToString(_ uuid: String) -> String? {
        let data = Unit.initialValues()
        
        do {
            let encodedData = try JSONEncoder().encode(data)
            let storedData = String(data: encodedData, encoding: .utf8)
            return storedData
        } catch let error {
            print(error)
        }
        
        return ""
    }
}

2021-07-23

by Seok

Step.2

Storage 구조 수정

  • Storage 데이터 불러오는 속도 개선을 위한 구조 수정
  • 앱 서비스 안정성(네트워크 불안정 등으로 부터)을 위한 구조 수정
  • DB I/O 최소화를 위한 구조 수정
  • 하기 구조를 바탕으로 기존 코드 수정 필요
스크린샷 2021-07-23 오후 12 47 58

2021-07-28

by Seok

Step.3

background진입 / app 종료 시에 firebase에 데이터 저장

  • 네트워크와 잦은 접촉을 막기위해 coredata에 먼저 저장
    • 무료서버이기 때문에 잦은 접촉은 네트워크 error유발 가능성이 있음, 네트워크 불안정시에도 data 보존을 위해 coredata에 먼저 저장
  • 백그라운드 진입 또는 앱 종료시에 해당 데이터를 firebase에 저장
  • app을 실행할 때는 firebase로 부터 storedData fetch
import Foundation
import RxSwift
import Firebase

final class DatabaseManager: DatabaseManagerType {
    
    private var ref: DatabaseReference
    private let uid = Auth.auth().currentUser?.uid ?? ""
    let data = [Unit]()
    
    init(_ ref: DatabaseReference) {
        self.ref = ref
    }
    
    func updateDatabase(_ info: NetworkDTO) {
        let unitData = DataFormatManager.transformToString(info.units)
        let moneyData = DataFormatManager.transformToString(info.money)
        let scoreData = DataFormatManager.transformToString(info.score)
        let adsData = DataFormatManager.transformToString(info.ads)
        ref.child("users").child(uid).setValue(["info": ["units": unitData, "money": moneyData, "score": scoreData, "ads": adsData]])
    }
    
    @discardableResult
    func getFirebaseData() -> Observable<NetworkDTO> {
        Observable.create { [unowned self] observer in
            self.ref.child("users").child(uid).getData { error, snapshot in
                if let error = error {
                    observer.onError(error)
                }
                
                if let data = snapshot.value as? [String: Any] {
                    observer.onNext(DataFormatManager.transformToLocalData(data))
                    observer.onCompleted()
                }
            }
            return Disposables.create()
        }
    }
}

DataFormat을 맞추기 위해 DataFormatManager 객체 메서드 추가 및 DTO 생성

import Foundation

struct UnitInformation: Decodable {
    let info: [String: String]
    
    enum Codingkeys: String, CodingKey {
        case info
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Codingkeys.self)
        let decodedInfo = try container.decode([String: String].self, forKey: .info)
        info = decodedInfo
    }
}

struct AdsInformation: Codable {
    let ads: [Bool]
    let lastUpdated: Date
    let gift: Int?
    
    static func empty() -> AdsInformation {
        return AdsInformation(ads: [], lastUpdated: Date(), gift: nil)
    }
}

struct NetworkDTO: Codable {
    let units: [Unit]
    let money: Int
    let score: Int
    let ads: AdsInformation
    
    static func empty() -> NetworkDTO {
        return NetworkDTO(units: [], money: 0, score: 0, ads: AdsInformation.empty())
    }
}
import Foundation
import Firebase

final class DataFormatManager {
    
    static func transformToLocalData(_ data: [String: Any]) -> NetworkDTO {
        
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: data, options: [])
            let info = try JSONDecoder().decode(UnitInformation.self, from: jsonData)
            let units = try JSONDecoder().decode([Unit].self, from: Data(info.info["units"]!.utf8))
            let money = try JSONDecoder().decode(Int.self, from: Data(info.info["money"]!.utf8))
            let score = try JSONDecoder().decode(Int.self, from: Data(info.info["score"]!.utf8))
            let ads = try JSONDecoder().decode(AdsInformation.self, from: Data(info.info["ads"]!.utf8))
            let result = NetworkDTO(units: units, money: money, score: score, ads: ads)
            return result
        } catch let error {
            print(error)
        }
        
        return NetworkDTO.empty()
    }
    
    static func transformToString<T: Encodable>(_ data: T) -> String? {
        do {
            let encodedData = try JSONEncoder().encode(data)
            let storedData = String(data: encodedData, encoding: .utf8)
            return storedData
        } catch let error {
            print(error)
        }
        
        return ""
    }
    
    static func transformToUnit(_ info: ItemInformation) -> Unit {
        let uuid = Int(info.uuid)
        let image = info.image ?? ""
        let level = Int(info.level)
        return Unit(uuid: uuid, image: image, level: level)
    }
    
    static func transformToMoney(_ info: MoneyInformation) -> Int {
        return Int(info.myMoney)
    }
}

2021-08-02

by Seok

Step.4

메서드 refactoring

  • 기존 UserDefaults가 확인했던 hasLaunchedOnce 기능을 firebase로 이전
  • 이전하면서 initValues 메서드 firebase에서 기능 구현 필요
  • 아래와 같이 로그인정보 확인하여 어떤 data를 return할지 변경하는 코드로 로직 수정
@discardableResult
func getFirebaseData() -> Observable<NetworkDTO> {
    Observable.create { [unowned self] observer in
        self.ref.child("users").child(uid).getData { error, snapshot in
            if let error = error {
                observer.onError(error)
            }
                
            if !snapshot.exists() {
                let initData = NetworkDTO(units: Unit.initialValues(), money: 0, score: 0, ads: AdsInformation.empty())
                observer.onNext(initData)
                observer.onCompleted()
            }
                
            if let data = snapshot.value as? [String: Any] {
                observer.onNext(DataFormatManager.transformToLocalData(data))
                observer.onCompleted()
            }
        }
        return Disposables.create()
    }
}
Clone this wiki locally