🔖 출처: https://benoitpasquier.com/error-handling-swift-mvvm/
(ErrorHandling을 어떻게 하면 좋을까 고민하며 공부했다. 이 글은 거의 위의 포스트의 번역 수준..? 이렇게 적용해볼 예정이다.)
🙋 Error를 어떻게 처리하는 것이 좋을까?
-
Closure as parameter: ViewController에서 처리하기(feat. completion handler)
// in CurrencyViewController self.viewModel.fetchCurrencies { result in switch result { case .failure(let error): // Display here case .success(_): // Nothing to do } }
문제점: viewModel과 ViewController 모두에서 에러 처리에 대한 switch case를 처리하고 있다.
각각의 역할에 대한 독립성을 추구하고, 의존성을 줄이는 MVVM의 목적에 부합하지 않아 보인다.
-
Dynamic value
뷰 모델의 error state를 뷰에서 바인드하는 방법
보통 데이터의 변경 처리에 대해서 이렇게 하는데, 오류가 있을 때에도 이렇게 하는건 어떨까?
struct CurrencyViewModel { var error : DynamicValue<ErrorResult?> = DynamicValue<ErrorResult?>(nil) func fetchCurrencies(_ completion: ((Result<Bool, ErrorResult>) -> Void)? = nil) { ... // later in the code switch result { case .success(let converter) : // reload data self.dataSource?.data.value = converter.rates case .failure(let error) : self.error.value = error } } } class CurrencyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() ... self.viewModel.error.addObserver(self) { error in // display error } self.viewModel.fetchCurrencies() } }
👉 fetchCurrencies에서 error가 발생하면,
error: DynamicValue<ErrorResult?>
에 넣어준다! 뷰컨에서 addObserver를 달아주면 api 오류가 발생할 때마다
//display error
에 작성한 코드가 실행된다.
❗️하지만 여기에도 문제가 있는데 우리가 에러를 컨트롤 할 수 없다는 것이다. 얼마나 많은 에러가 발생할 지 모른다.
-
다시 돌아온 Closure as property
struct CurrencyViewModel { var onErrorHandling : ((ErrorResult) -> Void)? func fetchCurrencies(_ completion: ((Result<Bool, ErrorResult>) -> Void)? = nil) { ... // later in the code switch result { case .success(let converter) : // reload data self.dataSource?.data.value = converter.rates case .failure(let error) : self.onErrorHandling?(error) } } } class CurrencyViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() ... self.viewModel.onErrorHandling = { error in // display error } self.viewModel.fetchCurrencies() } }
👉 클로저
onErrorHandling
: ErrorResult를 매개변수로 받는다. api에서 error가 발생했을 때, onErrorHandling에 전달해 주고,
ViewController에서 에러에 대한 처리를 한다. <- 처음이랑 뭐가 다르지?!
// in CurrencyViewController self.viewModel.fetchCurrencies { result in switch result { case .failure(let error): // Display here case .success(_): // Nothing to do } }
- 위의 코드는 결과 값을 그대로 받아와서, 성공 했을 때랑 실패했을 때의 case를 모두 적어주어야 했다.
onErrorHandling을 이용한 에러 대처는, 트리거가 발생했을 때 뷰를 컨트롤 할 수 있다는 장점이 있다.
만약에 어떤 에러에 대해서 유저에게 보여지는 화면에 대한 처리를 하고 싶지 않다면, 클로저에 처리를 하지 않으면 된다.
(Observer를 사용했을 때에는 특정 에러에 대한 컨트롤을 할 수 없었다. We can’t really do that with the previous one because we don’t know what other class is observing the value. <- ..?)