인풋, 아웃풋을 공부하면서 느낀 것은 완전 뇌절티비. 처음에 설명을 들으면서 그리고 후에 실습 코드를 따라치고, 내 과제에 적용하면서 구글링하며 머리에 껴넣으려고 했는데 도무지 이해가 안돼서 엉엉 울 뻔 하다가 같이 수업 듣는 수강생 두 분께 설명을 듣고 감이 왔다. 진쨔 감사합니다!!!!!!!!! 진심. 이거 이해하려는데 카페 노랫소리 왤케 시끄러; 집중 안돼서 다 뽀갤 뻔 했다.ㅠㅠ
Input/Output 패턴을 통한 비즈니스 로직 구분짓기
여튼,
Input/Output 패턴을 적용해주는 이유가 뷰컨에 있는 비즈니스 로직을 모두 뷰모델에서 처리해주기 위해서다.
내가 이해 안됐던 부분이 이 말을 듣고 이해가 됐다.
<ViewModel>
그니까, 자잘자잘한 비즈니스 로직까지도 모두 뷰모델에서 처리해주기 위해서
- 사용자의 입력 이벤트를 Input에 정의하고,
- View에 넘겨줄 데이터를 Output에 정의하는 것이당.
글구 이제 transform 메소드에서는 Input으로 받은 이벤트에 대해 비즈니스 로직 처리를 통해 Output으로 반환한다.
final class SearchViewModel: ViewModelType {
var userList = PublishSubject<SearchUser>()
struct Input {
let searchText: ControlProperty<String?>
}
struct Output {
let userList: PublishSubject<SearchUser>
let searchText: Observable<String>
}
func transform(input: Input) -> Output {
let userList = userList
let searchText = input.searchText
.orEmpty
.debounce(.seconds(1), scheduler: MainScheduler.instance)
.distinctUntilChanged()
return Output(userList: userList, searchText: searchText)
}
}
<View>
그러면 View(Controller)의 bindViewModel 함수에서는
- Input 객체를 만들어주고,
- transform 함수를 통해서 Output 객체를 반환해서 사용한다.
private func bindViewModel() {
let input = SearchViewModel.Input(searchText: searchView.searchBar.rx.text)
let output = searchViewModel.transform(input: input)
searchViewModel.userList // Output VM -> VC
.withUnretained(self)
.bind { (vc, user) in
var snapshot = NSDiffableDataSourceSnapshot<Int, Result>()
snapshot.appendSections([0])
snapshot.appendItems(user.results)
vc.dataSource.apply(snapshot, animatingDifferences: true)
}
.disposed(by: disposeBag)
// searchView.searchBar.searchTextField.rx.text // Input VC -> VM
// .orEmpty
// .debounce(.seconds(1), scheduler: MainScheduler.instance)
// .distinctUntilChanged() // 요놈들을 비즈니스 로직으로 생각했음
output.searchText
.withUnretained(self)
.bind(onNext: { (vc, value) in
vc.searchViewModel.requestSearchUser(query: value, page: 50)
})
.disposed(by: disposeBag)
searchView.collectionView.rx.itemSelected // 이것은 코디네이터.. 그렇군..
.withUnretained(self)
.bind(onNext: { (vc, item) in
vc.pushDetailView(item)
vc.searchView.collectionView.deselectItem(at: item, animated: true)
})
.disposed(by: disposeBag)
}
ViewModelType 프로토콜을 통한 ViewModel 추상화
Input/Output 패턴을 적용하다보면 ViewModel 코드가 반복되어 나타난다.
그럴 때 프로토콜을 통해서 ViewModel에 구현해줘야 하는 프로퍼티와 메소드를 선언해두면 해당 프로토콜을 채택 시에 어떤 프로퍼티와 메소드를 구현해줘야 하는지 쉽게 알 수 있다. 컴파일러가 알려주그든..
protocol ViewModelType {
associatedtype Input
associatedtype Output
func transform(input: Input) -> Output
}
여기 보면 associatedtype이 뭔가 싶은데,
Input / Output 구조체는 처음에 사용하기 전까지는 "특정 타입"이라고 단정지을 수 없다. Int인지 String인지 알 수가 없잖아??? 쓰기 전까지는??? 약간 제네릭이랑 비슷한 것임... 그래서 해당 프로토콜을 채택하면 처음에 typealias를 통해 Input / Output 이라는 별명을 가진 아이에게 붙여줄 type을 결정하라고 나온다. 결정하면 이때 타입이 정해지는 것인... 그런 바이브
여튼, 프로토콜을 만들어 줄 때는 타입을 모르니까 그때 타입을 단정짓기 어려워서 associatedtype을 써준다.
추가적으로 프로토콜이나 제약을 줄 수도 있다고 함..
기존 뷰컨에서 자잘한 비즈니스 로직들.. 긍까 아래 사진 코드 부분들까지도 뷰모델로 빼줬단 것임..
참고 블로그
'⭐️ 개발 > Rx' 카테고리의 다른 글
7. Traits - ControlProperty, ControlEvent, Driver (0) | 2023.01.16 |
---|---|
6. Binder (0) | 2023.01.16 |
[Rx] Error Operator (0) | 2023.01.16 |
[Rx] TIL - Rx 개념 재복습 및 총정리 (0) | 2022.10.31 |
[Rx] Observable, Observer, Disposable, Subject, Relay (4) | 2022.10.26 |