[2교시] RxSwift 활용하기 - 쓰레드의 활용과 메모리 관리
1. Merge: Observable 여러개를 묶어서 하나의 Observable로 만들어줌.
2. Zip: 위 아래 데이터가 하나씩 생성되면 그것들을 쌍으로 하나 만들어서 내려줌.
3. CombineLatest: 쌍이 하나 없으면 못내려가는 zip과 달리 가장 최근에 나온 것과 쌍으로 만들어줌.
// zip: Observable들을 합쳐서 처리
let jsonObservable = downloadJson(MEMBER_LIST_URL)
let helloObservable = Observable.just("hello world")
_ = Observable.zip(jsonObservable, helloObservable) { $1 + "\n" + $0 }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
})
[3교시] RxSwift 활용범위 넓히기 - UI 컴포넌트와의 연동
Subject
- 어떤 데이터를 보내야할지가 미리 정해진 스트림인 Observable과 달리 Subject는 Observable 밖에서 데이터를 주입할 수 있다.
- Subject는 데이터를 컨트롤하여 새로운 값을 넣어줄 수도 있고, 구독할 수도 있는 양방향성을 가진다.
- Observable.create 했을 때 받는 emitter의 역할과 비슷하나 create 안에서 쓰이는게 아니라 이미 만들어진 Observable 밖에서 사용이 가능하다.
- Subject를 만들어놓으면 외부에서 onNext로 데이터를 넣어줄 수 있다.
- 일반 Observable처럼 Subscribe도 가능하다.
1. AsyncSubject
여러개가 구독을 하더라도 데이터를 내려보내지 않고 complete되는 시점에서 가장 마지막에 있던 것들을 모두 내려주고 complete 시킨다.
2. PublishSubject
- Subject에 누군가가 subscribe 할 수 있음.
- 내부에서 데이터를 생성하면 그 데이터를 그대로 내려줌.
- 다른 subscriber가 또 새롭게 Subscribe하면 그 때 Subscribe하고 있는 모든 애들한테 데이터 전달.
- 데이터를 넣어주면 그 시점에 전달이 됨.
//lazy var menuObservable = Observable.just(menus) //menus에 대한 observable
// Observable.just면 밖에서 컨트롤 못하니 바뀔 수 있어야하니까 뭔가 바뀔 수 있는 형태로의 전환이 필요함
// PublishSubject로 만들어줌 [Menu] 타입으로
lazy var menuObservable = PublishSubject<[Menu]>()
// [Menu]를 받을 예정. [Menu]가 외부로부터 주어지면 그때마다 observable이 계속 동작할 예정.
lazy var itemsCount = menuObservable.map {
$0.map { $0.count }.reduce(0, +)
// menuObservable의 값이 바뀔대마다 count의 총합으로 바꿔서 밑으로 내려보내주는 observable.
}
lazy var totalPrice = menuObservable.map {
$0.map { $0.price * $0.count }.reduce(0, +)
// menuObservable(menus)의 값이 바뀌면 그 값이 바뀔때마다 다시 계산되어서 총합이 내려옴.
// menuObservable의 값이 바뀔때마다 가격의 총합으로 계산하여 밑으로 내려보내주는 observable.
init() {
let menus: [Menu] = [
Menu(id: 0, name: "튀김1", price: 1000, count: 0),
Menu(id: 0, name: "튀김1", price: 1000, count: 0),
Menu(id: 0, name: "튀김1", price: 1000, count: 0),
Menu(id: 0, name: "튀김1", price: 1000, count: 0),
]
menuObservable.onNext(menus)
}
// - MenuListViewModel -
var totalPrice: PublishSubject<Int> = PublishSubject()
// - MenuViewController -
//totalPrice는 Observable인데 map으로 currencyKR로 바꾼 다음 susbcribe onNext로 가져와서 self.totalprice의 텍스트에 집어넣음.
viewModel.totalPrice
.scan(0, accumulator: +) //0부터 시작해서 새로운 값이 들어오면 기존의 값이랑 더하겠다는 의미
.map { $0.currencyKR() }
.observeOn(MainScheduler.instance)
.bind(to: totalPrice.rx.text )
//맨 처음 subscribe에 의해 값이 바뀔때마다 onNext로 전달해서 값이 계속 바뀜. updateUI()를 계속 호출할 필요가 없음.
.disposed(by: disposeBag)
3. BehaviorSubject
- BehaviorSubject는 기본값을 하나 가지고 있음.
- 누군가 Subscribe 하자마자 기본값 데이터를 하나 내려주고 시작한다. (아직 데이터가 생성되지 않았을 때)
- 데이터가 생성될 때마다 계속 내려보내줌.
- 누군가 새롭게 중간에서 subscribe 하면 가장 최근에 발생했던 그 값을 미리 내려주고 나서 그 다음부터 발생하는 데이터를 똑같이 모든 구독하는 애들한테 전달해줌.
lazy var menuObservable = BehaviorSubject<[Menu]>(value: [])
// BehaviorSubject는 초기값을 가진다.
// viewmodel이 생성되는 시점에 onNext(menus)로 넘어오는 menus 어레이가 넣어진다.
// 나중에 tableview가 subscribe했을 때 가장 최근에 들어온 menus 어레이를 그대로 받아옴.
ex) menuObservable 같은 subject는 UI와 연결되어있음. 에러가 난다고 해서 스트림이 끊어지면 UI에서도 다 끊어지기 때문에 끊어지면 안됨.
4. ReplaySubject
- Subscribe 했을때 데이터를 순서대로 내려줌. (여기까지 PublishSubject와 비슷함)
- 두번째로 subscribe하는 애가 있으면 여태까지 발생했던 모든 데이터를 한꺼번에 다 내려줌.
- 그 이후에 발생하는 것은 같이 받을 수 있음.
- 여태까지 전달받았던 것을 한꺼번에 받고 시작.
RxCocoa
- RxSwift의 요소들을 UIKit에 extension한 형태
- UILabel에 .rx라는 것을 제공해줌. Binder 타입으로 rx.text라는 것을 가질 수 있음. (bind하면 Subscribe하지 않고 처리 가능)
- UI 작업의 특징
- 항상 메인 스레드에서 돌아야함. (observeOn 사용)
- UI 처리 도중 에러가 발생하면 작업이 끝나게 되고 해당 스트림은 재사용할 수 없다.
- 에러가 나더라도 스트림이 끊어지지 않도록 처리해야 함. ex) .catchErrorJustReturn("")
// UI에 대해서는 아래 두 줄의 작업이 항상 필요함.
.catchErrorJustReturn("")
.observeOn(MainScheduler.instance)
viewModel.itemsCount
.map { "\($0)" }
.catchErrorJustReturn("") //에러가 나면 그냥 빈 문자열로 내림 (에러가 났을 경우, UI Stream 끊어지는 것을 방지하기 위해서)
.observeOn(MainScheduler.instance)
.bind(to: itemCountLabel.rx.text) // bind를 하면 순환참조 없이 사용 가능. 즉, 순환참조 없이 들어온 데이터 값을 그대로 전달해줌.
.disposed(by: disposeBag)
// 그러나 항상 이렇게 처리하기 귀찮음. 그래서 나온게 -> Driver
- Observable / Driver : UI용으로는 driver로 제공
- Driver는 항상 메인 스레드에서 작동함. (observeOn 사용하지 않아도 됨.)
- 에러가 나는 경우에 대해 onErrorJustReturn으로 바꿔서 처리. ex) .asDriver(onErrorJustReturn: "")
// subscribe나 bind가 아닌 driver에 대해서는 drive를 사용.
// driver로 바뀌면 이 것은 에러가 나는 경우에 대해서 onErrorJustReturn로 바꿔서 처리.
viewModel.itemsCount
.map { "\($0)" }
.asDriver(onErrorJustReturn: "")
.drive(itemCountLabel.rx.text)
.disposed(by: disposeBag)
- Subject / Relay: UI용으로는 Relay로 제공
- BehaviorRelay
- 스트림이 끊어지지 않는 Subject
- BehaviorRelay는 Subject와 같지만 에러가 나도 끊어지지 않는다. (에러 무시)
- next, error, complete도 호출되지 않음. (complete되거나 error가 나오지 않기 때문에)
- 오로지 받을 수 밖에 없기 때문에 onNext() 대신 accept()를 사용한다.
- BehaviorRelay
// MARK: - MenuListViewModel -
lazy var menuObservable = BehaviorRelay<[Menu]>(value: [])
// MARK: - MenuViewController -
func changeCount(item: Menu, increase: Int) {
_ = menuObservable
.map { menus in
menus.map { m in
if (m.id == item.id) {
return Menu(id: m.id,
name: m.name,
price: m.price,
count: max(m.count + increase, 0)) // 카운트 '-'로 못내려가게 처리
} else {
return Menu(id: m.id,
name: m.name,
price: m.price,
count: m.count)
}
} //새로운 Menu가 만들어져서 리턴됨
}
.take(1) //한번만 수행하고 observable을 끝낼 것이다라는 의미. disposable 처리 할 필요 없어짐.
.subscribe(onNext: {
self.menuObservable.accept($0)
})
}
[4교시] RxSwift 를 활용한 아키텍쳐 - 프로젝트에 MVVM 적용하기
- MVC
- Input을 Controller가 IBAction의 형태로 받는다. 결과물에 대한 출력도 Controller에서 한다.
- UIKit에 종속되는 View, Controller와 달리 Model은 특정 플랫폼에 종속되지 않으므로 테스트가 가능하다.
- MVP
- Controller의 역할을 제한하고자 ViewController를 View쪽으로 넘기고, ViewController가 가지고 있떤 로직 부분만 따로 떼어 Presenter로 뺀다. (화면에 그려져야 될 요소에 대한 로직을 Presenter로 옮김.)
- View에 Input이 들어오면 Presenter에게 물어본다. Presenter는 어떠한 처리를 진행해서 이러한 내용들(눈에 보여져야될 모든 요소들)을 그리라고 View에게 시킴.
- View와 Presenter는 1:1 관계이다
- 모든 판단과 로직은 View가 하지 않고 Presenter가 함.
- MVVM
- MVP 패턴에서 View와 Presenter의 쌍이 여러개 만들어져야 하는 문제점 발생. 그냥 Presenter를 공통으로 하나 쓰이면 되는거 아닌가 하는 아이디어가 나옴.
- ViewModel이 화면에 무엇을 그리라고 지시하지 않음. View -> ViewModel 단방향 형태
- View가 ViewModel을 바라보면서 그 값이 바뀌면 View 스스로 갱신한다.
반응형
'RxSwift' 카테고리의 다른 글
RxSwift in 4 hours 정리(1) (0) | 2021.03.01 |
---|