머리말
요약
수업 시간에 배운 내용의 흐름을 위와 같이 정리해 보았다
(클릭하면 더 자세히 볼 수 있습니다)
본문
종류와 역할 및 특징
종류 | 역할 및 특징 | |
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 |
---|
댓글