저번시간엔 RxSwift의 ㄹㅇ 핵심인 Observable에 대해 정리해보았다.
RxSwift에는 Observable만큼 중요한 Subject, Relay가 있다.
Observable과 Observer의 단점은 Observable은 다른 Observable을 구독하지 못하고 Observer는 다른 Observer로 이벤트를 전달하지 못한다.
하지만 Subject는 Observable이면서 Observer이다. 즉 다른 Observable로부터 이벤트를 받아서 Observer(구독자)에 전달할 수 있다.
Subject의 종류에는 4가지가 있다.
PublishSubject는 가장 기본적인 형태의 Subject이다. PublishSubject는 구독 이후에 배출한 항목들만 옵저버에 전달한다. 따라서 o1에서 구독을 처음 했을 때는 전달되지 않는다.
또한 Subject는 completed나 error이벤트를 받는다. 받는 즉시 구독자에게 전달된다❗️
PublishSubject의 가장 중요한 특징은 Subject가 생성되는 시점과 구독자가 구독을 시작하는 시점 사이에 전달되는 이벤트는 그냥 사라진다❗️ 이런 사라지는 문제를 해결하기 위해서는 ReplaySubject나 Cold Observable을 사용해야 한다.
let disposeBag = DisposeBag()
enum MyError: Error {
case error
}
let subject = PublishSubject<String>
subject.onNext("Hello")
let o1 = subject.subscribe { print(">> 1", $0) }
o1.disposed(by: disposeBag)
subject.onNext("RxSwift")
let o2 = subject.subscribe { print(">> 2", $0) }
o2.disposed(by: disposeBag)
subject.onNext("Subject")
subject.onCompleted()
let o3 = subject.subscribe { print(">> 3", $0) }
o3.disposed(by: disposeBag)
==================================================
console 결과
>> 1 next(RxSwift)
>> 1 next(Subject)
>> 2 next(Subject)
>> 1 completed
>> 2 completed
>> 3 completed
BehaviorSubject는 PublishSubject와 동일한 방식으로 동작한다. 다만 Subject를 생성하는 방식과 구독할 때 차이가 있다.
아래 코드에서 보면 BehaviorSubject는 PublishSubject와 달리 초기값을 지정할 수 있다. 따라서 구독시에도 이 초기값이 next로 전달된다❗️
또한 BehaviorSubject에서 새로운 옵저버를 추가했을때는 어떻게 될까??
print("BehaviorSubject2 >>", $0) 이렇게 하나 더 추가했을 때 생성 시점에 만들어진 next이벤트를 전달하고 있다가 새로운 옵저버에 전달한다. 이후에 Subject에 새로운 next 이벤트가 전달되면 기존에 저장되어 있던 이벤트를 교체한다.
결과적으로 가장 최신 next이벤트를 옵저버에 전달한다❗️
또한 completed나 error 이벤트가 전달되면 저장되어 있는 next 이벤트는 옵저버에게 전달되지 않고 onCompleted나 error 이벤트가 전달된다.
let disposeBag = DisposeBag()
enum MyError: Error {
case error
}
let p = PublishSubject<Int>()
p.subscribe { print("PublishSubject >>", $0) }
.disposed(by: disposeBag)
let b = BehaviorSubject<Int>(value: 0)
b.subscribe { print("BehaviorSubject >>", $0) }
.disposed(by: disposeBag)
b.onNext(1)
b.subscribe { print("BehaviorSubject2 >>", $0) }
.disposed(by: disposeBag)
b.onCompleted()
b.subscribe { print("BehaviorSubject3 >>", $0) }
.disposed(by: disposeBag)
==================================================
console 결과
BehaviorSubject >> next(0)
BehaviorSubject >> next(1)
BehaviorSubject2 >> next(1)
BehaviorSubject >> completed
BehaviorSubject2 >> completed
BehaviorSubject3 >> completed
ReplySubject는 가장 최신 이벤트만을 저장할 수 있는 BehaviorSubject와 달리 지정된 수만큼 이벤트를 지정할 수 있다.
아래 코드를 보면 ReplaySubject는 지정된 버퍼 크기 만큼 최신 이벤트를 저장하고 새로운 구독자에게 전달한다. 또한 버퍼는 메모리에 저장되므로 메모리 관리가 필요하다.
completed이벤트를 전달하면 버퍼에 저장된 이벤트가 전달되고 completed 이벤트가 전달된다.
error 이벤트도 마찬가지이다.
let disposeBag = DisposeBag()
enum MyError: Error {
case error
}
let rs = ReplaySubject<Int>.create(bufferSize: 3)
(1...10).forEach { rs.onNext($0) }
rs.subscribe { print("Observer 1 >>", $0)}
.disposed(by: disposeBag)
rs.subscribe { print("Observer 2 >>", $0)}
.disposed(by: disposeBag)
rs.onNext(11)
rs.subscribe { print("Observer 3 >>", $0)}
.disposed(by: disposeBag)
rs.onCompleted()
rs.subscribe { print("Observer 4 >>", $0)}
.disposed(by: disposeBag)
==================================================
console 결과
Observer 1 >> next(8)
Observer 1 >> next(9)
Observer 1 >> next(10)
Observer 2 >> next(8)
Observer 2 >> next(9)
Observer 2 >> next(10)
Observer 1 >> next(11)
Observer 2 >> next(11)
Observer 3 >> next(9)
Observer 3 >> next(10)
Observer 3 >> next(11)
PublishSubject, BehaviorSubject, ReplaySubject는 Subject로 이벤트가 전달되면 즉시 구독자에게 전달한다.
반면 AsyncSubject는 completed이벤트가 전달되기 전까지 어떤 이벤트도 구독자에 전달하지 않는고 그 시점에 가장 최신 next 이벤트 하나를 구독자에 전달한다..
error 이벤트가 전달되는 경우에는 next 이벤트를 전달하지 않고 error 이벤트만 전달하고 종료된다.
let bag = DisposeBag()
enum MyError: Error {
case error
}
let subject = AsyncSubject<Int>()
subject
.subscribe { print($0) }
.disposed(by: bag)
subject.onNext(1)
subject.onNext(2)
subject.onNext(3)
subject.onCompleted()
==================================================
console 결과
next(3)
completed
RXSwift는 Subject를 wrapping하고 있는 Relay를 제공한다.
Relay도 Subject와 마찬가지로 이벤트를 구독자에 전달한다. Relay의 핵심은 Subject와 달리 next(accept)이벤트만 전달❗️한다.
relay에선 next 대신 accept 메서드를 이용한다.
completed와 error 이벤트는 전달받지도 않고, 전달하지도 않는다. 그래서 dispose 되기 전까지 종료되지 않는다.
이러한 특징으로 인해 UI 이벤트를 처리할 때 사용된다.
let bag = DisposeBag()
let prelay = PublishRelay<Int>()
prelay
.subscribe { print("1: \($0)")}
.disposed(by: bag)
prelay.accept(1)
====================
결과
1: next(1)
let bag = DisposeBag()
let brelay = BehaviorRelay(value: 1)
brelay.accept(2)
brelay
.subscribe { print("2: \($0)")}
.disposed(by: bag
brelay.accept(3)
print(brelay.value) // value는 읽기 전용 프로퍼티
====================
결과
2: next(2)
2: next(3)
3
let rrelay = ReplayRelay<Int>.create(bufferSize: 3)
(1...10).forEach { rrelay.accept($0) }
rrelay
.subscribe { print("3: \($0)")}
.disposed(by: bag)
====================
결과
3: next(8)
3: next(9)
3: next(10)