[RxSwift] RxCocoa traits - Driver

brick·2023년 3월 10일
0

RxSwift

목록 보기
2/2

✨Driver

This is the most elaborate trait. Its intention is to provide an intuitive way to write reactive code in the UI layer, or for any case where you want to model a stream of data Driving your application.

  • error를 방출하지 않습니다.
    error가 발생하면 애플리케이션이 사용자의 input에 반응하지 않습니다.(like operating system drivers)

  • main scheduler에서 observe합니다.
    UI element들은 thread safe하지 않음으로 main thread에서 observe되야합니다.

  • side effect를 공유합니다.
    share(replay: 1, scope: .whileConnected)



✨Practical usage example

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
    }

results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

코드의 intention

  • user input을 throttle
  • 서버에 접속해서 사용자 결과 목록을 가져온다
  • result를 두개의 UI element(table view, label)에 bind합니다.

코드의 problem

  • fetchAutoCompleteItems 가 error를 방출하면 모든걸 unbind 하고 UI는 새로운 query에 더 이상 반응하지 않을거다.

    • bind의 error 처리
      • debug mode: fatalError 발생
      • release mode: error log 처리
      /**
      Subscribes an element handler to an observable sequence.
      In case error occurs in debug mode, `fatalError` will be raised.
      In case error occurs in release mode, `error` will be logged.
    
      - parameter onNext: Action to invoke for each element in the observable sequence.
      - returns: Subscription object used to unsubscribe from the observable sequence.
      */
      public func bind(onNext: @escaping (Element) -> Void) -> Disposable {
          self.subscribe(onNext: onNext,
                         onError: { error in
                          rxFatalErrorInDebug("Binding error: \(error)")
                         })
      }
  • fetchAutoCompleteItems 가 background thread에서 results를 return하면 results는 UI element들을 background thread에서 bind하고 non-deterministic crashe가 발생한다.

  • Results는 2개의 UI element에 bind됐습니다. 각 UI element에 하나씩 HTTP Reqeust가 발생해 총 2번의 Request가 발생한다. 이는 의도된 동작이 아닙니다.


코드 개선

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)  // results are returned on MainScheduler
            .catchErrorJustReturn([])           // in the worst case, errors are handled
    }
    .share(replay: 1)                           // HTTP requests are shared and results replayed
                                                // to all UI elements

results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

driver 사용해서 개선

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)  // results are returned on MainScheduler
            .catchErrorJustReturn([])           // in the worst case, errors are handled
    }
    .share(replay: 1)                           // HTTP requests are shared and results replayed
                                                // to all UI elements

results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)


✨참고

0개의 댓글