RxSwift

RxSwift, Input과 Output으로 MVVM패턴 적용하기

TaeJoongYoon 2019. 4. 11. 00:30

RxSwift를 공부하면서 앱을 만들면서 MVVM패턴을 도전해보았습니다.
하지만 개발하면서 뭔가 MVVM답지 않게 프로그래밍한다고 느껴서 좀 더 찾아본 결과 Input, Output을 활용한 MVVM구조가 있어서
바로 적용해보았습니다 :)

RxSwift, Input과 Output으로 MVVM패턴 적용하기

github에서 많은 MVVM-Rx 예제들을 보면서 첫 틀을 갖추고 시작하여 많이 벗어나는 경우는 없었지만
이런 경우에는 살짝 애매했습니다.


Button Tap
-> Bind to ViewModel.buttonDidTapped
-> Business Logic in ViewModel
-> Subscribe ViewModel.result in View
-> Make event depends on result & Bind to ViewModel

 

맨 마지막 줄이 혼란의 핵심이었습니다.
EventBinding을 새로 Observable을 만들어서 해줘야 하나....? 고민도 되고
RxSwift 단톡방에도 여쭤봤는데 잘못 된 코드 같다고 하셔서 개선해야겠다는 생각밖에 없던 찰나
한 분이 Input, Output을 활용한 코드를 알려주셔서 더 찾아보았습니다.

 

기존의 제가 잘못짠 MVVM같은 경우는

 

간단하게 작성하겠습니다 :)

// View 
class ViewController {
    Observable<String>.create { observer -> Disposable in 
           observer.onNext("string")
        return Disposables.create()
    }
    .bind(to: viewModel.query)
    .disposed(by: self.disposeBag)
}

// ViewModel
struct ViewModel {
    let query = PublishSubject<String>()

    let result = query
        .flatMapLatest {
            return API.call($0)
        }
        .asDriver(onErrorJustReturn: ...)
}

 

지금보니 엉망이군요

 

기존 UI의 Event와는 연결을 잘 시켰지만 viewModel로 부터 받은 결과에 따라 다시 viewModel로 값을 전달할 때
위와 같이 만들었습니다....


개발하면서도 이건 아닌것 같은데 라는 느낌을 계속 받았죠...
그래서 위 코드를 Input, Output을 적용하여 ViewModel을 더 의미있게 분리하고 ReplaySubject를 이용하여 깔끔하게 개선했습니다.

 

이번에도 추상적으로 작성하겠습니다 :)

// View
class ViewController {
    viewModel.inputs.query("string")
}

// ViewModel
protocol ViewModelInputs {
    func query(string: String)
}

protocol ViewModelOutputs {
    var result: Driver<...> { get }
}

protocol ViewModelType {
    var inputs: ViewModelInputs { get }
    var outputs: ViewModelOutputs { get }
}

final class ViewModel: ViewModelType, ViewModelInputs, ViewModelOutputs {

    var inputs: ViewModelInputs { return self }
    var outputs: ViewModelOutputs { return self }

    // Input
    private let _query = ReplaySubject<String>.create(bufferSize: 1)
    func query(string: String) {
        self._query.onNext(string)
    }

    let result = _query
        .flatMapLatest {
            return API.call($0)
        }
        .asDriver(onErrorJustReturn: ...)
}

 

이제야 깔끔해졌군요...

 

코드를 위와 아래, 둘 다 자세히 쓴건 아니지만 확실히 위는 아닌것 같습니다.

 

아래 같은 경우 Input과 Output이 확실하게 구분이 되어 ViewModel의 Data 흐름이 더 직관적으로 나타나고
제가 혼란스러웠던 부분은 ReplaySubject + func을 활용하여 깔끔하게 해결했습니다.

 

물론 이보다 더 낫고 효율적인 구조를 가진 코드가 있겠지만
제가 이번 MVVM-Rx를 처음으로 공부하면서 배운 것은 여기까지 입니다...!
앞으로도 다르고 새로운 것에 도전해봐야겠습니다 😀