본문 바로가기

iOS_RxSwift

RxSwift + ReactorKit 에러 트래킹 Custom 방식에 대한 정리

기존에 RxSwift ErrorTracker 를 통해 공통으로 요청에대한 에러를 처리하는 방법에 대해 정리해놓은것이 있다.

ErrorTracker를 선언해놓고, .trackError하는 방식으로 다음 코드와 같다 (ErrorTracker 정리에 상세히 정리되어 있음)

 

apiService.exampleLogin() // RxSwift Observable 요청
  .trackActivity(self.activity)  // RIBs 사용하는 경우 presenter.activity
  .trackError(self.errorTracker) // RIBs 사용하는 경우 presenter.errorTracker
  .subscribe(onNext: { [weak self] _ in
  
  }).disposed(by: disposeBag)

장점은 여러 요청에 대해 한번에 대응이 가능하다는 것이다.

예를 들면 error가 났을 경우 error message에 대한 Alert만 보여주면 되는 경우 다음과 같이 대응하여 일괄로 처리가 가능하다.

 

// Way #1.
    errorTracker.asObservable()
      .subscribe(onNext: { [weak self] in
      	print($0.message)
        // self?.openAlert($0.message)
      }).disposed(by: disposeBag)
      
  // Way #2.
    errorTracker.asObservable()
      .flatMap { [unowned self] in self.openAlert(msg: $0.message) }
      .subscribe()
      .disposed(by: disposeBag)
      
 // openAlert 함수가 정의되어 있다고 가정

하지만 특정 요청의 경우 이 요청에 대한 에러가 났을 경우 Custom한 후속처리를 요구 할 수 있다.

이때 .catchError를 통해 대응했던 코드를 정리하고자 한다.

RIBs 정리 포스팅에서 SampleInteractor의 코드에서

ReactorKit를 활용하기 위해 Async라는, 말그대로 비동기 응답에 대한 Model를 만들어서 대응하는 코드가 있다.

 

extension SampleInteractor: Reactor {
  func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .requestLogin:
      let reset = Observable.just(Mutation.setLoginResult(Async.uninitialized))
      let update = apiService.requestLogin()
        .asObservable()
        .trackActivity(presenter.activity)
        .map { _ in Mutation.setLoginResult(Async.success(Void())) }
        .catchError { Observable.just(Mutation.setLoginResult(Async.error($0 as? CustomedError))) }
      return Observable.concat(update, reset)
  }

  func reduce(state: State, mutation: Mutation) -> State {
    var state = state
    switch mutation {
    case let .setLoginResult(result):
      state.result = result
    }

    return state
  }
}

Async Model 에 대한 코드는 다음과 같다.

 

import Foundation

enum State {
  case uninitialized
  case loading
  case error
  case success
}

private let asyncError = CustomedError(message: "async error")

enum Async<T>: Equatable {
  case uninitialized
  case loading
  case error(CustomedError?)
  case success(T)
  
  var state: State {
    switch self {
    case .uninitialized: return .uninitialized
    case .loading: return .loading
    case .error: return .error
    case .success: return .success
    }
  }
  
  func successValue() throws -> T {
    switch self {
    case .success(let value): return value
    default: throw asyncError
    }
  }
  
  func errorValue() throws -> CustomedError {
    switch self {
    case .error(let error):
      guard let error = error else { throw asyncError }
      return error
    default: throw asyncError
    }
  }
  
  static func == (lhs: Async<T>, rhs: Async<T>) -> Bool {
    return lhs.state == rhs.state
  }
}

UIViewController 에서는 성공, 실패(에러)를 이런식으로 받아서 처리한다.

 

reactor.state.map { $0.result }
  .distinctUntilChanged()
  .filter { $0.state == State.success }
  .map { try $0.successValue() }
  .observeOn(MainScheduler.instance)
  .subscribe(onNext: { [weak self] _ in
    // 성공처리
  }).disposed(by: disposeBag)

reactor.state.map { $0.result }
  .distinctUntilChanged()
  .filter { $0.state == State.error }
  .map { try $0.errorValue() }
  .observeOn(MainScheduler.instance)
  .subscribe(onNext: { [weak self] error in
    // 에러처리
    print(error.message)
  }).disposed(by: disposeBag)