Taehyeon-Kim/SeSAC

[221101] TIL

Taehyeon-Kim opened this issue · 1 comments

MVVM Input/Output Modeling

데이터에 대한 처리의 흐름을 분리해놓는 작업
데이터가 흘러가는 방향에 집중

  • VC -> VM
  • VM -> VC

데이터의 흐름을 명확하게 구분해서 볼 수 있다는 장점이 있다.

output.stepButtonDidTap
    .bind { _ in
        print("SHOW ALERT")
    }
    .disposed(by: disposeBag)
  • button tap을 굳이 숨길 필요가 있냐라는 생각이 들 수 있지만, 서버 통신이나 DB 접근 등의 비즈니스 로직을 추가로 처리할 수 있기 때문에 이를 ViewModel 내에 숨겨서 연산을 진행한다. 중요한 것은 output을 구독해서 사용한다는 사실이다.

코드 구조

ViewModel

struct ValidationViewModel {
    
    struct Input {
        let text: ControlProperty<String?>
        let stepButtonDidTap: ControlEvent<Void>
    }
    
    struct Output {
        let validText: Driver<String>
        let validation: Observable<Bool>
        let stepButtonDidTap: ControlEvent<Void>
    }
    
    func transform(input: Input) -> Output {
        let validText = BehaviorRelay(value: "닉네임은 최소 8자 이상 필요해요").asDriver()
        
        // 데이터스트림을 바꿔가는 작업도 ViewModel로 숨겨놓음
        let validation = input.text
            .orEmpty
            .map { $0.count >= 8 }
            .share()
        
        return Output(
            validText: validText,
            validation: validation,
            stepButtonDidTap: input.stepButtonDidTap
        )
    }
}

ViewController + Bind

private func bind() {
    let input = ValidationViewModel.Input(
        text: nameTextField.rx.text,
        stepButtonDidTap: stepButton.rx.tap)
    let output = viewModel.transform(input: input)
    
    output.stepButtonDidTap
        .bind { _ in
            print("SHOW ALERT")
        }
        .disposed(by: disposeBag)
    
    output.validText
        .asDriver()
        .drive(validationLabel.rx.text)
        .disposed(by: disposeBag)
    
    output.validation
        .bind(to: stepButton.rx.isEnabled, validationLabel.rx.isHidden)
        .disposed(by: disposeBag)
    
    output.validation
        .withUnretained(self)
        .bind { vc, value in
            vc.stepButton.backgroundColor = value ? .systemPink : .lightGray
        }
        .disposed(by: disposeBag)
}