Observable & Observer
- 데이터를 변경해줄 수 있는 이벤트가 있고, 이 이벤트에 따라서 변경되는 뷰, 로직이 있다.
- 즉, 이벤트를 방출할 수 있는 Observable이 있고, 이벤트를 처리하는 Observer가 있다.
- Observable과 Observer를 통해 데이터의 흐름(= Stream)을 통제할 수 있고
- Operator를 통해 Stream을 변경, 조작할 수 있다.
사용자에게 텍스트 필드로 입력값을 받아서, 해당 입력값을 닉네임을 지정한다고 할 때, 그림으로 표현하면 아래와 같다.
그리고 코드로 구현하면 아래와 같이 작성할 수 있다.
simpleTextField.rx.text
.orEmpty
.withUnretained(self)
.bind { vc, value in
vc.nickname = value
}
.disposed(by: disposeBag)
- 하지만 Observable은 subscribe를 하지 못하기 때문에 이벤트 방출만 할 수 있고 이벤트에 대한 처리는 할 수 없다.
- Observer 역시 받은 이벤트를 다른 Observer에게 전달하지 못한다.
위와 같이 simpletTextField.rx.text에서 바로 이벤트에 대한 전달을 받을 수 없는 것이다.
코드로 구현하면 위와 같이 오류가 나타난다.
- 그래서 !! Observer와 Observable 역할을 모두 할 수 있는 Subject가 등장한다.
Subject
- Subject는 이벤트에 대한 emit과 subscribe를 모두 할 수 있다.
- Observable로서 이벤트를 받을 수 있고
- 전달받은 이벤트를 Observer로 전달할 수 있다.
- Subject에는 4가지 종류가 있다.
- Publish Subject
- Behavior Subject
- Replay Subject
- Async Subject
- 하지만, UI에 좀 더 적합한 형태가 필요했고 Subject 내부에서 Wrapping하여 Relay를 제공한다.
Relay
Relay에는 Publish Relay, Behavior Relay 2가지가 있고 Subject와 거의 유사한 특성을 갖고 있다.
Relay VS Subject
Subject와 Relay의 가장 큰 차이는 Relay의 경우 Completed와 Error 이벤트를 받지 못한다는 것이다.
UI가 Error와 Completed 이벤트를 받게 된다면, 더 이상 next 이벤트를 전달 받을 수 없게 되고, 이는 반응형의 장점이 사라지게 되는 것이다.
Relay는 Completed와 Error 이벤트를 받지 못하기에 disposed가 되기 전까지 subscribe가 해제 되지 않는다.
completed 또는 error를 받으면 disposed 상태가 되는데 받지 못하므로 disposed가 호출되는 deinit 시점에 dispose되거나 아니면 직접 처리를 해주어야 한다.
그래서 Relay는 next 이벤트만 처리한다.
next 이벤트를 accept이라는 키워드로 사용한다.
종류
Relay의 종류는 크게 두가지로
- Publish Relay
- Behavior Relay
가 있다.
RxSwift | RxCocoa |
Subject | Relay |
Subscribe(next, error, completed) | Bind / Drive |
subscribe -> bind -> drive
#subscribe
button.rx.tap
.observe(on: MainScheduler.instance)
.withUnretained(self)
.subscribe { vc, _ in
vc.label.text = "안녕 반가워"
}
.disposed(by: disposeBag)
Observable을 구독한다.
Background Schedular에서 동작할 가능성(네트워크 통신, 파일 다운로드 등)이 있기 때문에 Observable의 데이터 흐름을 MainSchedular(메인 쓰레드)에서 동작할 수 있도록 변경한다.
#bind
button.rx.tap
.withUnretained(self)
.bind { vc, _ in
vc.label.text = "안녕 반가워"
}
.disposed(by: disposeBag)
subscribe와 유사하지만 Main Schedular의 동작과 error 이벤트를 방출하지 않는 특성을 발휘할 수 있는 bind를 통해 값을 주입해줍니다.
button.rx.tap
.map { "안녕 방구야" }
.bind(to: label.rx.text)
.disposed(by: disposeBag)
tap의 ControlEvent<Void>를 map operator를 통해 데이터의 흐름을 조작한다.
ControlEvent<Void> 타입이 String 타입으로 변경이 되면서, label.rx.text로 간단하게 bind를 할 수 있다.
#driver
button.rx.tap
.map { "안녕 똥방구야" }
.asDriver(onErrorJustReturn: "Error")
.drive(label.rx.text)
.disposed(by: disposeBag)
stream이 공유될 수 있는 특성인 Driver Traits를 활용한다.
Traits & Driver
- UI 처리에 특화된 Observable을 Trait이라고 하고,
- RxSwift에서 제공해주는 Observable,
- RxCocoa에서 제공해주는 Observable이 있다.
- Traits에 해당하는 Observable의 공통적인 특성은 Main Thread에서 실행된다는 점이다.
- 그리고 Error 이벤트도 없다.
- 또한, Traits 중 signal을 제외하고 share 연산자가 내부적으로 사용이 되기 때문에 Subscribe를 하게 될 경우 동일한 시퀀스를 공유하게 된다.
Driver
- Main 쓰레드에서의 실행을 보장한다.
- Observable의 UI로 특화된 형태이므로 subscribe만 할 수 있고 값을 변경할 수는 없다.
- bind와 다르게 stream을 공유가 된다.
- bind는 subscribe의 별칭
- drive는 내부적으로 share(replay: 1, scope: .whileConnected)가 구현되어 있다.
실습
Publish Relay 를 사용해서 이미지 리스를 받아오는 코드를 작성해보자.
먼저 ViewModel에서 로직 관련 부분을 작성하기 때문에 서버 통신 코드를 작성한다.
var publishRelay = PublishRelay<[Photo]>()
func requestPhotoListWithPublishRelay() {
ListAPIManager.shared.fetchPhotoList { [weak self] photoList, statusCode, error in
guard let self = self else { return }
guard let photoList = photoList else { return }
guard let statusCode = statusCode else { return }
if statusCode == 200 {
self.publishRelay.accept(photoList)
} else {
self.publishSubjectList.onError(ListError.requestError)
}
}
}
PublishRelay 형태로 변수를 만들고 accept 키워드를 통해서 이벤트 처리를 한다.
이 때, 오류가 나게 된다면, Error 타입을 확장한 enum의 종류로 onError 를 보내고 이를 핸들링 할 수 있도록 한다.
그리고 이렇게 전달 받은 이벤트에 대해서 ViewController에서 뷰가 그려지는 시점에 리스트가 보여야 하므로
viewModel.requestPhotoListWithPublishRelay()
viewModel.publishSubjectList
.withUnretained(self)
.bind { vc, value in
var snapshot = NSDiffableDataSourceSnapshot<Int, Photo>()
snapshot.appendSections([0])
snapshot.appendItems(value)
self.dataSource.apply(snapshot)
}
.disposed(by: disposeBag)
서버 통신 코드를 작성해서 데이터를 받으면,
이 데이터에 대한 변경을 감지하여 UICollectionView에 보여준다.
'Swift > RxSwift' 카테고리의 다른 글
[RxSwift] Rx+MVVM으로 로그인 화면을 만들어보자. (0) | 2022.10.31 |
---|---|
[🌱SeSAC] Observable VS Subject, Drive & Relay (6) | 2022.10.28 |
[🔥Rx뿌셔] Subject VS Observable (0) | 2022.10.26 |
[🌱SeSAC] Disposable, Observable, Subject (0) | 2022.10.26 |
[🔥Rx뿌셔] Disposable (0) | 2022.10.25 |