이제 MVVM 패턴을 적용해서 투두 앱을 만들어보자! 이전 글과 마찬가지로 새롭게 알게 된 내용이나 기억하고 싶은 내용 위주로만 작성할 예정이다.
init
메소드 안에서 옵저버를 등록한다.Model 디렉토리 안에 있는 하위 디렉토리들은 아래와 같다.
📂 Core Data
📂 Models
📂 Services
📂 Extensions
📂 Constants
RxSwift는 비동기적으로 코드를 작성할 수 있게 도와주는 라이브러리다. 새로운 데이터를 받으면 동작할 코드를 연속적이고 독립적으로 간단하게 작성할 수 있다.
Observable은 한 개 이상의 Observer를 가질 수 있다.
Input/Output 접근법은 서로 다른 컴포넌트와 이벤트를 바인딩하고 RxSwift를 더 체계적으로 사용하기 위한 방법이다.
class ExampleViewModel {
var output: Output!
var input: Input!
struct Input {
let text: PublishRelay<String>
}
struct Output {
let title: Driver<String>
}
init() {
let text = PublishRelay<String>()
let capsTitle = text
.map({
return text.uppercased()
})
.asDriver(onErrorJustReturn: "")
input = Input(text: test)
output = Output(title: capsTitle)
}
}
Input/Output 접근법을 사용해서 ViewModel을 구현한 예시이다.
UITextField
에서 입력한 문자열 값이 방출될 것이다.UITextField
에서 받아온 값을 UI 컴포넌트에 전달한다.나중에 알게 되었는데,
ViewModel 초기화 시점에 Input의 스트림을 생성하지 않고 View에게 Input을 전달받고 Output 스트림을 생성해서 리턴하는 메소드를 정의하는 방법도 있다고 한다.
참고1, 참고2
그럼 ViewModel과 View는 어떻게 바인딩해줄까?
class ExampleView {
func bind() {
textfield.rx.text
.bind(to: viewModel.input.text)
.disposed(by: disposeBag)
viewModel.output.title
.drive(titleLabel.rx.text)
.disposed(by: disposeBag)
}
}
UITextField
의 text
파라미터를 ViewModel의 text 변수(Input)와 바인딩한다. 사용자가 텍스트필드에 텍스트를 입력하면 ViewModel이 이를 받게 된다.UILabel
의 text
파라미터와 바인딩한다. 사용자가 타이핑을 하면 ViewModel의 text가 대문자로 변환되고, View에 전달되어서 화면에 표시된다.흠…🤔
drive
는 Drive
에서만 정의되며, bind(to:)
대신 사용할 수 있다고 한다.. → 그니까 텍스트필드의 텍스트를 구독해서 뷰모델의 input에 알려주고, 뷰모델의 output을 구독해서 타이틀라벨의 텍스트에게 알려주는…..bind(to:)
메소드는 다음과 같은 형태로 작성되어 있다.
public func bind(to relays: PublishRelay<Element>...) -> Disposable {
bind(to: relays)
}
이 메소드는 새 구독을 생성하고 항목을 publish relay들에게 보내는 메소드라고 한다.
안에 있는 bind
메소드가 어떻게 구현되어 있는지 더 뜯어보자.
private func bind(to relays: [PublishRelay<Element>]) -> Disposable {
subscribe { e in
switch e {
case let .next(element):
relays.forEach {
$0.accept(element)
}
case let .error(error):
rxFatalErrorInDebug("Binding error to publish relay: \(error)")
case .completed:
break
}
}
}
이 bind
메소드를 호출하는 Observable을 구독하고, 새 항목이 방출되면 파라미터로 받은 relay에게 그 항목을 전달하는 것 같다!
to
라는 파라미터 이름을 방출된 항목을 받는 친구를 가리키는 거라고 생각해야겠다.
drive()
는 종류가 다양하다. Observer와 Relay 모두 파라미터로 받을 수 있다.
public func drive<Observer: ObserverType>(_ observers: Observer...) -> Disposable where Observer.Element == Element {
MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
return self.asSharedSequence()
.asObservable()
.subscribe { e in
observers.forEach { $0.on(e) }
}
}
이 메소드도 새 구독을 생성하고 항목을 observer에게 보내주는 메소드라고 한다. 대신 메인 스레드에서만 호출된다.
결론은 bind(to:)
와 drive()
모두 메소드를 호출하는 Observable을 구독하는 것이고, 파라미터로 받은 Observer가 변경된 값을 전달받는 듯 하다!