082_Rx(Input, Output)+MVVM

2022. 11. 1. 20:26SeSAC/수업정리

-. 뷰모델을 통해 UI로직과 비지니스 로직을 분리하면 객체, 이벤트가 많아졌을때 쉽게 확인 할 수 있다. 이 때 Input, Output으로 데이터 흐름을 구분 할 수 있다. 

-. VC->VM : Input(버튼탭, 텍스트필드 입력하는 텍스트 등), VM->VC : Output(뷰 상태, 텍스트, 화면전환, 얼럿 등) 

-. Input, Output 사용 전에는 뷰컨트롤러에서 이벤트를 처리했지만 Input을 사용해서 관찰대상이 되는 이벤트의 처리를 뷰모델에 전달하고, 뷰모델에서는 Output을 사용해서 연산을 적용한 이벤트를 뷰컨트롤러에 전달한다.(뷰컨틀롤러에서는 메서드의 반환값을 통해 값에 접근할 수 있음 ex.뷰컨트롤러에서 transform의 반환값인 Output구조체의 매개변수에 접근)

 

Input, Output 사용 전

-. 뷰모델

class ValidationViewModel {
    
    let validText = BehaviorRelay(value: "닉네임은 최소 8자 이상 필요합니다")
    
}

 

-. 뷰컨트롤러

 viewModel.validText
            .asDriver() //relay짝궁은 DRIVER
            .drive(validationLabel.rx.text)
            .disposed(by: disposeBag)
        
        let validation = nameTextField.rx.text //String?타입
            .orEmpty //String타입
            .map { $0.count >= 8 } //Bool타입
            .share() //subject 내부에 share가 있기 때문에 subject에서는 share 따로 안써도 됨
        
        validation
            .bind(to: stepButton.rx.isEnabled, validationLabel.rx.isHidden)
            .disposed(by: disposeBag)
        
        validation
            .withUnretained(self)
            .bind { (vc, value) in
                let color: UIColor = value ? .systemPink : .lightGray
                self.stepButton.backgroundColor = color
            }
            .disposed(by: disposeBag)

 

Input, Output 사용 후

-. 뷰모델

class ValidationViewModel {
    
    let validText = BehaviorRelay(value: "닉네임은 최소 8자 이상 필요합니다")
    
    struct Input {
        let text: ControlProperty<String?> //let validation = nameTextField.rx.text에서 text타입인 ControlProperty로 프로퍼티 생성
        let tap: ControlEvent<Void> //stepButton.rx.tap
    }
    
    struct Output {
        let validation: Observable<Bool> //validation에 대한 연산을 거치고 뷰컨트롤러로 전달할 값의 타입을 가진 프로퍼티 생성
        let tap: ControlEvent<Void> //input, output 동일
        let text: Driver<String> //viewModel.validText에서 구독할 대상의 타입가진 프로퍼티 생성
    }
    
    //input->output 바꿔주는 메서드
    func transform(input: Input) -> Output{
        let valid = input.text //let input = ValidationViewModel.Input(text: nameTextField.rx.text, tap: stepButton.rx.tap)에서 input을 통해 text에 접근할 수 있으므로 let validation = nameTextField.rx.text과 동일
            .orEmpty //String타입
            .map { $0.count >= 8 } //Bool타입
            .share() //subject 내부에 share가 있기 때문에 subject에서는 share 따로 안써도 됨
        
        let text = validText.asDriver() //asDriver까지 연산메서드에서 처리
        
        return Output(validation: valid, tap: input.tap, text: text)
    }
}

 

-. 뷰컨트롤러

let input = ValidationViewModel.Input(text: nameTextField.rx.text, tap: stepButton.rx.tap) //텍스트필드 입력내용을 뷰모델로 보냄
        let output = viewModel.transform(input: input) //
        
        output.text
            .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
                let color: UIColor = value ? .systemPink : .lightGray
                self.stepButton.backgroundColor = color
            }
            .disposed(by: disposeBag)