Skip to content

Latest commit

 

History

History
126 lines (90 loc) · 4.37 KB

[22.01.26] MVVM과 Error 코드 처리.md

File metadata and controls

126 lines (90 loc) · 4.37 KB

#ErrorHandling in MVVM

🔖 출처: https://benoitpasquier.com/error-handling-swift-mvvm/
(ErrorHandling을 어떻게 하면 좋을까 고민하며 공부했다. 이 글은 거의 위의 포스트의 번역 수준..? 이렇게 적용해볼 예정이다.)

🙋 Error를 어떻게 처리하는 것이 좋을까?

  1. 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의 목적에 부합하지 않아 보인다.

  2. 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에 작성한 코드가 실행된다.

​ ❗️하지만 여기에도 문제가 있는데 우리가 에러를 컨트롤 할 수 없다는 것이다. 얼마나 많은 에러가 발생할 지 모른다.

  1. 다시 돌아온 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. <- ..?)