본문 바로가기
iOS/RxSwift

MVVM) Input-Output 패턴 적용하기 (feat. RxSwift)

by Dev.Andy 2023. 11. 8.

머리말

요약

클릭하면 더 자세히 볼 수 있습니다

수업 시간에 배운 내용의 흐름을 위와 같이 정리해 보았다

(클릭하면 더 자세히 볼 수 있습니다)

본문

종류와 역할 및 특징

종류   역할 및 특징
View / ViewController 1. UI를 구성
2. 이벤트를 감지하여 ViewModel에 입력값으로 전달
3. ViewModel이 전달한 출력값을 화면에 띄움
Input View/ViewController의  이벤트를 감지하여 ViewModel에 보낼 데이터
ViewModel 1. UI 로직과 비즈니스 로직의 분리
2. MVC 패턴에서 과도한 기능을 분리
Output ViewModel에서 가공하여 View/ViewController에 표현할 데이터
bind 1. View/ViewController의 클래스 메서드
2. 나머지 요소 (View / ViewModel / Input / Output)를 서로 연결

코드

1) View / ViewController


  
import UIKit
import RxSwift
import RxCocoa
final class ValidateViewController: UIViewController {
@IBOutlet private var nameTextField: UITextField!
@IBOutlet private var validationLabel: UILabel!
@IBOutlet private var nextButton: UIButton!
private let viewModel = ValidateViewModel()
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bind()
}
private func bind() {
//...
}
}

2) Input

2-1) 구조체 Input은 클래스인 ViewModel에 있다


  
class ValidateViewModel {
struct Input {
let text: ControlProperty<String?> // nameTextField.rx.text
let tap: ControlEvent<Void> // nextButton.rx.tap
}
}

2-2) 구조체 인스턴스 input은 View/ViewController의 메서드 bind()에 있다


  
final class ValidateViewController: UIViewController {
private func bind() {
let input = ValidateViewModel.Input(text: nameTextField.rx.text, tap: nextButton.rx.tap)
}
}

3) ViewModel


  
class ValidateViewModel {
struct Input {
let text: ControlProperty<String?>
let tap: ControlEvent<Void>
}
struct Output {
let text: Driver<String>
let tap: ControlEvent<Void>
let validation: Observable<Bool>
}
func transform(input: Input) -> Output {
let validation = input.text
.orEmpty
.map { $0.count >= 8 }
let validText = BehaviorRelay(value: "닉네임은 8자 이상입니다")
.asDriver()
return Output(
text: validText,
tap: input.tap,
validation: validation
)
}
}

4) Output

4-1) 구조체 Output은 클래스인 ViewModel에 있다


  
class ValidateViewModel {
struct Output {
let text: Driver<String>
let tap: ControlEvent<Void>
let validation: Observable<Bool>
}
}

4-2) 구조체 인스턴스 output은 View/ViewController의 메서드 bind()에 있다


  
final class ValidateViewController: UIViewController {
private func bind() {
let output = viewModel.transform(input: input)
}
}

5) bind


  
final class ValidateViewController: UIViewController {
private func bind() {
let input = ValidateViewModel.Input(text: nameTextField.rx.text, tap: nextButton.rx.tap)
let output = viewModel.transform(input: input)
output.text
.drive(validationLabel.rx.text)
.disposed(by: disposeBag)
output.validation
.bind(to: nextButton.rx.isEnabled, validationLabel.rx.isHidden)
.disposed(by: disposeBag)
output.validation
.bind(with: self) { owner, value in
let color: UIColor = value ? .systemRed : .lightGray
owner.nextButton.backgroundColor = color
}
.disposed(by: disposeBag)
output.tap
.bind(with: self) { owner, _ in
print("nextButton CLICKED")
}
.disposed(by: disposeBag)
}
}

꼬리말

MVVM에 정답은 없다

위와 같은 코드나 흐름이 무조건 정답은 아니다.

관점에 따라 다른 코드

어디까지를 Input으로 보고 어디까지를 Output로 봐야 하는지는 사람마다, 코드마다 다르다.

연습만이 살 길

따라서 무작정 하나의 코드를 고집하는 것이 아닌 여러 코드를 구현해 가면서 자신에게 맞는 로직과 주관을 갖는 게 중요하다 :)

코드로 실습하기

블로그 링크

LSLP) MVVM과 RxSwift를 이용한 반응형 이메일 입력 화면 구현 (feat: Input-Output & BehaviorRelay)

(2023-11-15 추가)

'iOS > RxSwift' 카테고리의 다른 글

RxSwift) Hot Observables vs Cold Observables  (2) 2024.04.17

댓글