티스토리 뷰

반응형

일단 feature브랜치로 firebase-fetch를 하나 만들고 작업을 시작합니다 

memos의 어떤 변화든 관찰하겠다(?) 하는 코드인데, 

let rootRef = Database.database().reference()
rootRef.child("memos").observe(.value) { snapshot in

    let memosDic = snapshot.value as? [String: Any] ?? [:]
    for (key, value) in memosDic {
        print("key \(key) value\(value)")
    }
}

딕셔너리를 출력해보면 key는 자동으로 만들어준 메모의 아이디 / value는 해당메모의 속성에 대한 딕셔너리이다 

key -Lfu2XqePDtQA6KcyvJs
value{
    title = "\Ud1a0\Ub9c8\Ud1a0\Uc0ac\Uae30";
}

key -Lfu-oGQnhiwrYXIq6Ch
value{
    title = "\Uacc4\Ub780\Uc0ac\Uae30";
}

key -Lfu1yT46GqUtdvXRmaF
value{
    title = "\Uc6b0\Uc720\Uc0ac\Uae30";
}

key -Lfu2cBWv7-CNnYD76UF
value{
    title = "\Ub2f9\Uadfc\Uba39\Uae30";
}

 

그래서 FirebaseManager 클래스에 fetchAll 메소드를 이런식으로 만들어주고, 

  class func fetchAll() -> Observable<[Memo]> {
      let rootRef = Database.database().reference()
      rootRef.child("memos").observe(.value) { snapshot in
          var memos: [Memo] = []
          let memosDic = snapshot.value as? [String: Any] ?? [:]
          for (key, _) in memosDic {
              if let memoDic = memosDic[key] as? [String: Any], let memo = Memo(dic: memoDic) {
                  memos.append(memo)
              }
          }
      }
      return Observable.just(memos)
  }

Memo 구조체에도 dictionary를 받는 옵셔널 이니셜라이저를 만들어준다 

struct Memo {
    let title: String
    
    init(title: String) {
        self.title = title
    }
    
    init?(dic: [String: Any]) {
        guard let title = dic["title"] as? String else {
            return nil
        }
        
        self.title = title
    }
}

extension Memo {
    func toDictionary() -> [String: Any] {
        return ["title": title]
    }
}

 

그리고 MemosViewModel에 가서 

이니셜라이즈에 fetchAll하는 로직을 다음과 같이 추가해준다 

struct MemosViewModel {
    
    struct State {
        var memos: BehaviorRelay<[Memo]> = BehaviorRelay.init(value: [])
    }
    
    struct Action {
        let deleteMemo = PublishSubject<Memo>()
        let changeMemo = PublishSubject<Memo>()
    }
    
    let state = State()
    let action = Action()
    private let bag = DisposeBag()
    
    init() {
        action.deleteMemo.subscribe(onNext: { memo in
            print("delete \(memo)")
        }).disposed(by: bag)
        
        action.changeMemo.subscribe(onNext: { memo in
            print("change \(memo)")
        }).disposed(by: bag)
        
        FirebaseManager.fetchAll()
            .bind(to: state.memos)
            .disposed(by: bag)
    }
}

 

그리고 실행시켜보면, MemosViewController의 이 부분때문에

private var viewModel = MemosViewModel()

Firebase를 쓰기 전 가장 먼저 불려야하는 이 코드보다 

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

      FirebaseApp.configure()
      return true
  }

MemosViewModel 의 이니셜라이저 안에있는 FirebaseManager.fetchAll()이 먼저 실행되어서 

크래쉬가 난다...! ( Firebase를 쓸 준비가 안되었는데 접근해서 그렇다...!!! )

 

그래서 코드를 이렇게 바꾸어주고 

private var viewModel: MemosViewModel!

viewDidLoad에서 뷰모델을 생성해준다 그러면 Firebase.configure()이 먼저 실행되어서 크래쉬가 안난다..! 

  override func viewDidLoad() {
      viewModel = MemosViewModel()
      bindTableView()
      tableView.rx.setDelegate(self).disposed(by: bag)
  }

 

    @IBOutlet weak var tableView: UITableView! {
        didSet {
            tableView.rx.setDelegate(self).disposed(by: bag)
            bindTableView()
        }
    }


이렇게 tableView didSet에 해주었던 코드들도 viewModel이 생성된 다음에 실행되도록 같이 viewDidLoad에 옮겨주었다 

bindTableView 메소드에 viewModel에 접근하는 코드가 들어가기 때문 --! 

 

 

그리고 실행해보면 메모 데이터들을 받아오는 fetchAll 메소드가 항상 [] 의 옵져버블을 리턴한다는 문제가 발생한다

class func fetchAll() -> Observable<[Memo]> {
    let rootRef = Database.database().reference()
    rootRef.child("memos").observe(.value) { snapshot in
        var memos: [Memo] = []
        let memosDic = snapshot.value as? [String: Any] ?? [:]
        for (key, _) in memosDic {
            if let memoDic = memosDic[key] as? [String: Any], let memo = Memo(dic: memoDic) {
                memos.append(memo)
            }
        }
    }
    return Observable.just(memos)
}

서버에 갔다가 온 후 리턴되야하는 코드인데 바로 리턴해버리면 항상 empty 배열만 리턴하기 때문이다 

그럴때는 Observable.create 를 사용해주면 된다 

  class func fetchAll() -> Observable<[Memo]> {
      return Observable<[Memo]>.create { observer in
          let rootRef = Database.database().reference()
          rootRef.child("memos").observe(.value) { snapshot in
              var memos: [Memo] = []
              let memosDic = snapshot.value as? [String: Any] ?? [:]
              for (key, _) in memosDic {
                  if let memoDic = memosDic[key] as? [String: Any], let memo = Memo(dic: memoDic){
                      memos.append(memo)
                  }
              }
              observer.onNext(memos)
          }
          return Disposables.create()
      }
  }

 

 

 

이제 준비 끝 ---!! 실행해보면 fetch 가 잘된다 

 

 

아이패드 시뮬레이터 하나 더 띄우고 큐브라떼 라는 메모를 저장해보면

바로바로 fetch가 된다

웹과 앱 모두 새로고침이나 reload하지 않아도 바로 데이터가 갱신되는데 신기하당 Firebase 짱-! 

 

 

 

+ 데이터 fetch할 때 순서가 보장안되는 것은 딕셔너리로 변환하는 과정 때문이다 (딕셔너리는 unordered)

snapshot.value는 데이터를 순서대로 잘 준다 

그래서 딕셔너리를 key에 따라서 sort해주는 코드를 추가한다 

memosDic.sorted(by: {$0.key < $1.key}) 

 

fetchAll 메소드의 최종 코드는 이렇게 되겠당...! 

  class func fetchAll() -> Observable<[Memo]> {
      return Observable<[Memo]>.create { observer in
          let rootRef = Database.database().reference()
          rootRef.child("memos").observe(.value) { snapshot in
              var memos: [Memo] = []
              let memosDic = snapshot.value as? [String: Any] ?? [:]
              for (key, _) in memosDic.sorted(by: {$0.key < $1.key}) {
                  if let memoDic = memosDic[key] as? [String: Any], let memo = Memo(dic: memoDic){
                      memos.append(memo)
                  }
              }
              observer.onNext(memos)
          }
          return Disposables.create()
      }
  }

 

 

순서대로 잘 나온다 : )

 

observeSingleEvent / queryLimited 등 재밌는 기능들이 더 있지만 나중에 봐야지 🙃

반응형
댓글