앞 포스팅에서는 Swift5가 RxSwift6에 어떻게 적용되었는지 살펴보았습니다. 이번 포스팅에서는 RxSwift6에 새롭게 추가된 Observable type 과 Operation을 알아보고, 사용성을 어떻게 개션 되었는지 살펴 보도록 하겠습니다! 👀
Infallible
Infallible
은 RxSwift6에서 새롭게 추가된 Observable type입니다. Observable
과 다르게 .error
를 방출(emit)하지 않습니다. 이는 RxCocoa
의 Relay와 비슷한 개념(relay에서는 .completed
와 .error
를 방출 하지 않습니다.)으로 .error
로 인해 subscribe가 종료되는 것을 방지할때 유용하게 쓰일거 같습니다.
1
2
3
4
5
6
7
8
9
10
Infallible<String>.create { observer in
observer(.next("Hello"))
observer(.next("World"))
observer(.completed)
// 여기에서 .error를 방출할 수 없습니다.
return Disposables.create {
// Clean-up
}
}
물론 비슷한 기능을 하는 Driver
와 Signal
이 있습니다. 하지만 이 둘은 RxCocoa에 UI작업을 위해 Observable
이용해 MainScheduler
에서 작업이 되도록 구현되어 있습니다. 하지만 Infallible
은 완전히 새로운 Observable type
이라는 점에서 차이가 있습니다! 🆕
ReplayRelay 연산자 추가
RxSwift6에서는 ReplayRelay
와 PublishRelay
에서 ReplayRelay
를 추가하여 3가지의 Relay를 제공합니다. Relay에서는 .completed
와 .error
를 방출 하지 않아, .error
와 .completed
로 인한 subscribe가 종료되는 것을 방지 할 수 있습니다. 사용법은 ReplaySubject
와 동일합니다.
1
2
3
4
5
// Subject
ReplaySubject<Int>.create(bufferSize: 3)
// Relay
ReplayRelay<Int>.create(bufferSize: 3)
withUnretained 추가 🌟
RxSwift로 구현된 ViewModel를 stream을 View에 Binding하기 위해서는 self
에 대한 참고를 capture lists를 통해 받습니다. 이와 같은 작업은 매번 Binding을 할때마다 반복적으로 작업을 해줘야 했습니다.
1
2
3
4
5
6
viewModel.importantInfo
.subscribe(onNext: { [weak self] info in
guard let self = self else { return }
self.doImportantTask(with: info)
})
.disposed(on: disposeBag)
이러한 반복을 멈추도록 withUnretained
operation 이 추가 되었습니다!👍 withUnretained
operation을 통해 미리 self
를 참조 받아 capture lists 없지 작업을 할 수 있습니다.
1
2
3
4
5
6
viewModel.importantInfo
.withUnretained(self) // (Object, Element) 듀플 반환
.subscribe(onNext: { owner, info in
owner.doImportantTask(with: info)
})
.disposed(by: disposeBag)
이제는 많은 예제의 bindViewmodel()
함수에 output
에 미리 self
참조를 받아서 사용할 것 같네요.
1
2
3
4
5
6
7
func bindViewModel() {
...
let output = viewModel.output.withUnretained(self)
// view와 bind 작업
...
}
**Observable를 위한 decode(type:decoder:)연산자 제공**
Swift의 Codable을 이용하여 data decode를 하는 Operation인 decode(type:decoder:)
추가 되었습니다.
1
2
3
service.rx
.fetchJSONUsers() // Observable<Data> 반환
.decode(type: [User].self, decoder: JSONDecoder()) // Observable<[User]> 반환
해당 Operation의 추가로 Rx를 이용한 네트워크 라이브러들의 decode operation이 변경될꺼 같습니다. 예로 RxMoya는 아래와 같이 decode 함수를 작성했습니다.(In History)
1
2
3
4
/// Maps received data at key path into a Decodable object. If the conversion fails, the signal errors.
func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) -> Single<D> {
return flatMap { .just(try $0.map(type, atKeyPath: keyPath, using: decoder, failsOnEmptyData: failsOnEmptyData)) }
}
driver(), emit()에 다중 바인딩 제공
RxSwift6에서 Driver
와 Signal
에서도 multi Binding을 지원합니다. 이로인해 RxSwif5에서는 각각의 view에 subscribe
를 해주던 작업을 한번에 할 수 있게 되었습니다.
1
2
3
4
5
6
7
8
9
10
11
// RxSwift5
viewModel.string.drive(input1)
viewModel.string.drive(input2)
viewModel.string.drive(input3)
viewModel.number.emit(input4)
viewModel.number.emit(input5)
// RxSwift6
viewModel.string.drive(input1, input2, input3)
viewModel.number.emit(input4, input5)
이는 Swift에 Variadic Parameters
를 사용하여 구현되었습니다. Variadic Parameters
는 파라미터로 특정 객체를 한개 이상 받을 수 있도록 합니다.
RxSwift5에서는 Drive
한개의 Observer
객체만 받아서 사용하고 있습니다.
1
2
3
4
5
// RxSwift5
public func drive<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
return self.asSharedSequence().asObservable().subscribe(observer)
}
RxSwift6에서는 (1) Variadic Parameters
이용하여 한개 이상의 Observer
객체만 받아서 (2) 각각의 Observer
에 element를 넘겨주도록 구현되었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
// RxSwift5
// (1) Variadic Parameters를 사용하여 한개 이상의 Observer을 받음
public func drive<Observer: ObserverType>(_ observers: Observer...) -> Disposable where Observer.Element == Element {
MainScheduler.ensureRunningOnMainThread(errorMessage: errorMessage)
return self.asSharedSequence()
.asObservable()
.map { $0 as Element? }
.subscribe { e in
// (2) 각각의 Observer에 element 넘겨줌
observers.forEach { $0.on(e) }
}
}
Binder가 RxCocoa에서 RxSwift로 이동
Binder
는 input, output을 간단히 Binding하기 위해 사용됩니다. 만약 Binder
가 없다면 output을 subscribe
받도록 설정해 주어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
// RxSwift5
// not use Binder
viewModel.isButtonEnable
.subscribe(onNext: { [weak self] in self?.myButton.isEnabled = $0 })
.disposed(by: disposeBag)
// RxSwift6
// use Binder
viewModel.isButtonEnable
.bind(to: myButton.rx.isEnabled)
.disposed(by: disposeBag)
하지만 이번 RxSwift6에서는 RxCocoa에 있는 Binding
이 넘어오면서, 간단한 bind
를 사용하기 위해 RxCocoa를 사용할 필요가 없어졌습니다. 🙌