-
[Rxswift] 03.SubjectsiOS/RxSwift 2021. 12. 19. 21:03
이 게시글은 raywenderlich, RxSwift: Reactive Programming with Swift 책을 참고하여 작성한 글입니다.
observerble은 RxSwift에서 필수적이지만, read-only이다.
할 수 있는것은, subscribe하고 그들이 생산하는 새로운 event를 알림받는것이다.
observable과 observer의 역할을 모두 할 수 있는 것이 있는데, 바로
Subject
이다.소개
example(of: "PublishSubject") { let subject = PublishSubject<String>() // 1 subject.on(.next("Is anyone listening?")) // 2 let subscriptionOne = subject .subscribe(onNext: { string in print(string) }) // 3 subject.on(.next("1")) subject.onNext("2") }
- subject에서 “Is anyone listening?”을 emit(publish)한다.
- 하지만 observer가 없기 때문에 아무것도 일어나지 않는다.
- subscriptionOne이 subject를 subscribe한다.
- 역시나 아무 일도 일어나지 않는다.
PublishSubject
는 emit하는 시점에 존재하는 subscriber에게만 emit하기 때문이다.
- subject에서 “1”, “2”을 emit(publish)한다.
- subscriptionOne이 현 시점에서 subject를 subscribe하고있기 때문에 event가 제대로 전달된다.
- 따라서 아래와 같은 결과가 나온다.
--- Example of: PublishSubject --- 1 2
종류
PublishSubject
,BehaviorSubject
,ReplaySubject
,AsyncSubject
총 4개의 subject type이 있다.- 또한, RxSwift는 Relay라는 개념을 제공한다.
PublishRelay
,BehaviorRelay
총 2개가 있는데, 이들은 모두 각각의 subject를 감싼 형태로 볼 수 있다.next event
만 relay, accept 할 수 있다.- 따라서
completed
orerror
event를 추가할 수 없고, non-termination sequence와 잘 어울린다.
💡 원래, realy는 RxCocoa의 일부로서 역할을 했다.
하지만 realy가 일반적으로 잘 사용되는 개념이기도 하고, Cocoa가 아닌 환경에서도 유용했다.
따라서 새로운 모듈로 분리되었다. RxCocoa는 이 새로운 모듈인 RxRealy에 의존한다.PublishSubject
- 해당 시점에 존재하는 subscriber에게 새로운 event를 알리고 싶을 때 사용된다.
- subscriber가 unsubscribe하거나, subject가
complete
orerror
event로 terminat됐을 때 까지 지속된다. - marble diagram으로 확인해보면 아래와 같다.
💡 맨 윗줄은 publish subject의 event를 나타내고, 그 아랫줄 부터는 subscriber를 나타낸다.
위로 향하는 화살표는 subscribe를 한 시점을 나타낸다.
아래로 향하는 화살표는 emit event를 나타낸다.- 첫 번째 subscriber은
1
이 emit된 이후에 추가되었으므로2
와3
에 대해서만 전달받는다. - 두 번째 subscriber은
2
가 emit된 이후에 추가되었으므로3
에 대해서만 전달받는다.
1) 2 1) 3 2) 3
- stop event라고 불리는
completed
orerror
event 이후에는, 새로운 subscriber에게 더 이상next event
가 전달되지 않고stop event
가 전달된다. - 예시
- 온라인 초대 서비스와 같은
time-sensitive data
를 다룰 때 publish subject가 유용하다.- 10:01에 접속한 사용자가 “9:59에 서비스가 1분 남았다는 알림”을 받을 일이 없기 때문이다.
- Publish subject는 새로운 subscriber에게 event를 반복하지 않기 때문에 아래와 같은경우에서 유용하다.
- “유저가 무언가를 탭했다는 이벤트”, 또는 “방금 도착한 알림 이벤트”
- 온라인 초대 서비스와 같은
BehaviorSubject
- publish subject와 비슷하지만, 새로운 subscriber에게 가장 최근의 event를 replay한다는 점에서 다르다.
- marble diagram으로 보면 아래와 같다.
💡 BehaviorSubject 는 항상 가장 마지막의 element를 emit하기 때문에, 초깃값을 설정해주어야 한다.
초깃값을 설정할 수 없다면, PublishSubject 를 사용하건, Optional 하게 모델링 해야한다.- 예시
- 가장 최근의 data를 가지고 view의 초기데이터를 설정할 때 유용하다.
- 유저 프로필 화면을 behavior subject로 연결해 가장 최신의 데이터를 설정할 수 있다.
- 새 subscriber에게 최근의 value를 전달하기 때문에 다음과 같은 상황에서 유용하다.
- “request is currently loading”, “the time is now 9:41” 의 state를 모델링할 때
- 가장 최근의 data를 가지고 view의 초기데이터를 설정할 때 유용하다.
ReplaySubject
- 가장 최근의 event를 선택한 size만큼 cache, buffer 한뒤, 새로운 subscription이 발생했을 때 해당 subscriber에게 buffer에 있는 모든 event를 emit한다.
- buffer size가 2일 때의 marble diagram은 아래와 같다.
- 첫 번째 subscriber은 최근의 event가 없기 때문에, 이후에 emit되는 event들만 받는다.
- 두 번째 subscriber은 그 즉시, subscribe하기 이전에 발생한 두 개의 event를 받는다.
- 이후에는 마찬가지로, emit되는 event들을 받는다.
🚨 replay subject를 사용할 때, buffer은 항상 메모리에 존재한다는 것을 명심해야한다. 만약 buffer size를 크게 잡거나, size가 큰 image를 buffer에 넣게 되면 과도한 메모리를 사용하게 된다.
🚨 또한, replay subject에 array를 담을 때에도 조심해야 한다. 각각의 emit된 element는 array이기 때문에, memery pressure가 발생하기 쉽다.
stop event(error, completed)
가 발생하기 이전의 buffer*을 계속해서 보유하고 있어서,stop event
이후의 subscriber에게 *buffer +stop event
를 모두 emit한다.- 만약 subscribe이전에
dispose()
가 발생했다면, 아래와 같은 문구가 나오게 된다.
3) Object `RxSwift...ReplayMany<Swift.String>` was already disposed.
- dispose bag에 넣는다면 이런 edge case를 피할 수 있다.
Relay
- relay는 subject를 그것의 replay하는 행동을 유지하면서 포장한것을 말한다.
accept(_:)
메소드를 사용하고,onNext
,onError
,onCompleted
는 사용할 수 없다.- // 21.01.05 추가 )
Relay는 onComplete, onError가 없기 때문에 끝나는시점이 없다고 볼 수 있다.
그렇기 때문에 직접 dispose를 구현해주지 않으면 메모리 누수가 발생하기 쉽다.
PublishRelay
- 코드를 살펴보자
example(of: "PublishRelay") { let relay = PublishRelay<String>() let disposeBag = DisposeBag() // 1 relay.accept("Knock knock, anyone home?") // 2 relay .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) // 3 relay.accept("1") }
- 1에서 새로운 element를 받긴 했지만, PublishSubject와 마찬가지로 당시 subscriber가 없기 때문에 아무것도 발생하지 않는다.
- 2에서 새로운 subscribe가 추가된 뒤, 3에서 추가적으로 element를 받아 아래와 같은 결과가 나온다.
--- Example of: PublishRelay --- 1
BehaviorRelay
- BehaviorSubject와 마찬가지로 초깃값이 필요하고, 가장 최신 혹은 초깃값을 새 subscriber에게 반복해서 제공한다.
- 이것의 특별한 힘은 현재의 값을 언제든 얻을 수 있다는 점이다.
- 그렇기에 명령형과 반응형 사이의 연결통로가 되어줄 수 있다.
- 코드를 살펴보자.
example(of: "BehaviorRelay") { // 1 let relay = BehaviorRelay(value: "Initial value") let disposeBag = DisposeBag() // 2 relay.accept("New initial value") // 3 relay .subscribe { print(label: "1)", event: $0) } .disposed(by: disposeBag) // 4 relay.accept("1") // 5 relay .subscribe { print(label: "2)", event: $0) } .disposed(by: disposeBag) // 6 relay.accept("2") }
- 1에서 새로운 BehaviorRelay를 생성하는데, 초깃값이 있기 때문에 타입을 추론할 수 있다.
- 짐작 가능하겠지만 결과는 아래와 같이 나온다.
--- Example of: BehaviorRelay --- 1) New initial value 1) 1 2) 1 1) 2 2) 2
- 또한 위에서 말했듯 명령형과 반응형의 연결통로가 될 수 있도록, 현재의 값을 확인할 수 있다.
print(relay.value) // 결과 2
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] Error Handling [2] (0) 2022.01.27 [RxSwift] Error Handling [1] (0) 2022.01.25 RxSwift로 리펙토링 맛보기 (0) 2022.01.03 [RxSwift] 02.Observables / raw (0) 2021.12.19 [RxSwift] 01.Fundamental / raw (0) 2021.12.19 - subject에서 “Is anyone listening?”을 emit(publish)한다.