-
[RxSwift] Error Handling [1]iOS/RxSwift 2022. 1. 25. 20:51
아래 코드는 Firebase/Auth의 전화번호 로그인과 idtoken발급에 대한 예제코드에 RxSwift를 적용한 것이다.
네트워크 레이어 분리를 위해서 viewModel과 다른 클래스에 메서드를 작성해 주었다.
분리하기 이전에는 네트워크 통신시 에러가 발생했을 때, errorToast관련 Subject에 accept만 해주고 onNext를 방출할 수 있었다.
하지만 분리한 뒤에는, 에러가 발생했을 때 viewModel에게 명확하게 에러가 발생했다는 것을 전달해 주기 위해서
Single(.failure)
을 사용해야겠다고 판단했다.해당 상황을 예제코드로 옮기면 아래와 같다.
// // ViewController.swift // Practice // // Created by SEUNGMIN OH on 2022/01/25. // import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var label: UILabel! @IBOutlet weak var textField: UITextField! @IBOutlet weak var button: UIButton! override func viewDidLoad() { super.viewDidLoad() bind() } func bind() { button.rx.tap.asObservable() .withLatestFrom(textField.rx.text.orEmpty) .flatMap(Network.shared.signin) .asDriver(onErrorRecover: { error in print(error) return .just(0) }) .map { String($0) } .drive(label.rx.text) .disposed(by: disposeBag) } } class Network { static var shared = Network() func signin(text: String) -> Single<Int> { return Single.create { single in if (text.count % 2) != 0 { single(.success(text.count)) } else { single(.failure(APIError.invalidFormat)) } return Disposables.create() }.debug() } } enum APIError: Error { case invalidFormat }
버튼이 눌릴 때마다 네트워크통신이 발생해야 하는데,
Single
은 한 번 이벤트를 방출하면 스트림이 끝나게 된다.따라서 어떤 이벤트가 발생하던 dispose된다.
Single
은 한번밖에 사용이 안된다는 것을 알았으니, Observable로 바꿔서 다시 실험해봤다.func signin(text: String) -> Observable<Int> { return Observable.create { observer in if (text.count % 2) != 0 { observer.onNext(text.count) } else { observer.onError(APIError.invalidFormat) } return Disposables.create() }.debug("signin") }
이제는 정상적인 값에 대해서는 dispose되지 않는다. 하지만
onErrorRecover
에서 에러이벤트를 정상적인 값으로 바꿔줬음에도 dispose되었다.asDriver
로 에러처리한 부분에 대해서도 디버그를 달아보니 아래와 같이 나왔다.error 이벤트를 바꾼다 하더라도 onError에 수반되어 발생되는 onComplete는 피할 수 없어보인다.
해결
https://airnauts.com/post/rx-swift-errors-done-right
https://github.com/ReactiveX/RxSwift/issues/729
위 두 레퍼런스를 참고하여 다른 방법으로 에러핸들링을 시도해봤다.
첫 번째 방법은
catch
operator을 이용한 방법이고, 두 번째 방법은Result
를 이용한 방법이다.catch
를 이용한 방법보다Result
를 이용한 방법이 훨씬 깔끔하다고 느껴졌기 때문에, 해당 방법만 기술하겠다. 궁금하다면 첫 번째 레퍼런스를 확인해보면 된다.Result
를 사용하면, 정상적인 값이던Error
든 상관없이 모두onNext
로 처리해줄 수 있기 때문에 네트워크 통신을 재활용해야할 때 사용하면 좋다고 생각한다.아직 익숙치 않아 깔끔하게 코드를 작성하지 못했지만, 이런식으로 작성해서 오류에 대한 처리도 하고 네트워크 메서드를 여러번 요청할 수 있게 된다.
func bind() { let text = BehaviorRelay<String>(value: "") button.rx.tap.asObservable().debug("buttonTap") .withLatestFrom(textField.rx.text.orEmpty) .flatMap(Network.shared.signin) .filter { result -> Bool in switch result { case .failure(let error): self.errorManager.accept(error) return false case .success: return true } } .debug("asDriver") .map { return try String($0.get()) } .bind(to: text) .disposed(by: disposeBag) text.asDriver() .drive(label.rx.text) .disposed(by: disposeBag) }
추가
meterialize
,demeterialize
를 활용해서 error handling을 할 수도 있다.'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] weak, unowned self에 대한 의문 (0) 2022.01.27 [RxSwift] Error Handling [2] (0) 2022.01.27 RxSwift로 리펙토링 맛보기 (0) 2022.01.03 [Rxswift] 03.Subjects (0) 2021.12.19 [RxSwift] 02.Observables / raw (0) 2021.12.19