지난 포스팅에서 Observable에 대해서 알아보았는데요 !
보통의 앱 개발에서 필요한 것은 실시간으로 Observable에 새로운 값을 수동으로 추가하고 subscriber에게 방출하는 방식입니다.
그러므로 Observable이자 Observer인 녀석이 필요하게 되는데 요 녀석을 Subject라고 합니다.
Subject의 종류
Subject = Observable + Observer 와 같이 행동합니다.
Subject는 .next 이벤트를 받고, 이런 이벤트를 수신할 때마다 subscriber에 방출합니다.
RxSwift에는 4가지 타입의 subject가 있습니다.
- PublishSubject : 빈 상태로 시작하여 새로운 값만을 subscriber에 방출
- BehaviorSubject : 하나의 초기값을 가진 상태로 시작하여 새로운 subscriber에게 초기값 또는 최신값을 방출
- RelaySubject : 버퍼를 두고 초기화하며 버퍼 사이즈만큼의 값들을 유지하면서 새로운 subscriber에게 방출
- Variable : BehaviorSubject를 래핑하고 현재의 값을 상태로 보존합니다. 가장 최신 / 초기 값만을 방출
PublishSubjects
PublishSubject는 구독된 순간 새로운 이벤트 수신을 알리고 싶을 때 용이합니다.
이런 활동은 구독을 멈추거나 .completed, .error 이벤트를 통해 Subject가 완전 종료될 때까지 지속됩니다.

첫 번째 줄은 subject를 배포한 것으로 두 번째 줄과 세 번째 줄이 subscriber들입니다.
아래로 향하는 화살표들은 이벤트의 방출, 위로 향하는 화살표들은 구독을 선언하는 것을 의미합니다.
첫 번째 subscriber는 1 다음에 구독합니다. 그러므로 1 이벤트는 받지 못하고, 2와 3을 받게 됩니다.
두 번째 subscriber는 같은 원리로 3만 받습니다.
코드로 예시를 살펴볼까요 ?
example(of: "PublishSubject") {
let subject = PublishSubject<String>()
subject.onNext("Is anyone listening?")
let subscriptionOne = subject
.subscribe(onNext: { (string) in
print(string)
})
subject.on(.next("1"))
subject.onNext("2")
// 1 두 번째 subscriber를 추가
let subscriptionTwo = subject
.subscribe({ (event) in
// .onNext 이벤트와 방출되므로 nil 확인 필수
print("2)", event.element ?? event)
})
// 2 subject에 3 추가 -> subscriptionOne과 subscriptionTwo에서 총 두 번 출력
subject.onNext("3")
// 3 subscriptionOne을 dispose하고 subject에 4 추가
// -> 출력 : 2) 3 & 2) 4
subscriptionOne.dispose()
subject.onNext("4")
// 4 이벤트 종료
// -> 출력 : 2) completed
subject.onCompleted()
// 5 이미 종료되었으므로 출력 및 인쇄 X
subject.onNext("5")
// 6
subscriptionTwo.dispose()
let disposeBag = DisposeBag()
// 7 subject 완전 종류 후 새로운 subscriber가 생긴다고 다시 subject 작동 X
// -> 출력 : 3) completed
subject
.subscribe {
print("3)", $0.element ?? $0)
}
.disposed(by: disposeBag)
subject.onNext("?")
}
언제 사용할까 ?
시간에 민감한 데이터를 모델링할 때 ! !
BehaviorSubjects
BehaviorSubject는 마지막 .next 이벤트를 새로운 구독자에게 반복한다는 점만 뺀다면 PublishSubject와 유사합니다.

PublishSubject와 다르게 첫 번째 이벤트가 발생한 후 첫 번째 구독자가 구독을 시작했음에도 직전의 값 1을 받습니다.
두 번째 구독자 역시 직전의 값 2을 받습니다.
역시 코드로 예시를 살펴봅시다 ~
// 1
enum MyError: Error {
case anError
}
// 2
// 이벤트 내 값이 있다면 값 출력하고 에러가 있다면 에러를 nil이라면 event만 출력
func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
print(label, event.element ?? event.error ?? event)
}
// 3
// BehaviorSubject는 초기값을 입력하여 생성
example(of: "BehaviorSubject") {
// 4
// BehaviorSubject는 항상 최신의 값만 방출하기 때문에 초기값 없이 만들 수 없음
// 만약 초기값을 줄 수 없는 경우에는 PublishSubject 사용해야 함
let subject = BehaviorSubject(value: "Initial value")
let disposeBag = DisposeBag()
}
// 6
subject.onNext("X")
// 5
// subject 구독 후 dispose
// -> 1) Initial value
// 6을 입력하면 구독 전 최신값이 "X"로 바뀌므로 출력 값도 바뀜
// -> 1) X
subject
.subscribe{
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
// 7
subject.onError(MyError.anError)
// 8
// -> 1) anError 2) anError
subject
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
}
언제 사용할까 ?
뷰를 가장 최신의 데이터로 미리 채울 때 ! !
예를 들어 유저 프로필 화면의 컨트롤을 BehaviorSubject에 바인드할 수 있는데 이렇게 하면 앱이 새로운 데이터를 가져오는 동안 최신 값을 사용해 화면을 미리 그려놓을 수 있겠죠 ~
RelaySubjects
RelaySubject는 생성시 선택한 특정 크기까지 방출하는 최신 요소를 일시적으로 캐시하거나 버퍼합니다.
그 다음 해당 버퍼를 새 구독자에게 방출합니다.

첫 번째 줄이 subject, 아래에 있는 애들이 구독자들입니다.
여기서 subejct의 버퍼 사이즈는 2입니다.
첫 번째 구독자는 subject와 함께 구독하므로 subject의 값들을 그대로 갖습니다.
두 번째 구독자는 subject가 두 개의 이벤트를 받은 후 구독하였지만 버퍼 사이즈 2만큼의 값을 받을 수 있습니다.
여기서 중요한 점 !
RelaySubject에서 사용되는 버퍼들은 메모리를 가지고 있다는 점입니다.
이미지나 arrray 같이 메모리를 크게 차지하는 값들은 메모리에 엄청난 부하를 주겠죠 ㅠ
코드로 살펴볼게요 ~
example(of: "ReplaySubject") {
// 1
// 버퍼 사이즈 2 가지는 RelaySubject 생성
let subject = ReplaySubject<String>.create(bufferSize: 2)
let disposeBag = DisposeBag()
// 2
// 1, 2, 3 세 개의 요소들 subject에 추가
subject.onNext("1")
subject.onNext("2")
subject.onNext("3")
/* 3
해당 subject에 대한 두 개의 구독자 생성
-> 1) 2
1) 3
2) 2
2) 3
*/
subject
.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
subject
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
subject.onNext("4")
subject.onError(MyError.anError)
subject.subscribe {
print(label: "3)", event: $0)
}
.disposed(by: disposeBag)
/* Prints:
1) 4
2) 4
-> 4가 추가되기 전 이미 구독중이기 때문에 4만 출력
1) anError
2) anError
3) 3
3) 4
-> 새로운 구독자이므로 마지막 2개의 요소 출력
3) anError
*/
// 에러 이벤트만 받게 됨
subject.dispose()
}
언제 사용할까 ?
BehaviorSubject처럼 최근의 값 외에 더 많은 것을 보여주고 싶을 때 ! !
예를 들어 검색창과 같이 최근 5개의 검색어를 보여주고 싶을 때 사용하면 좋겠네요 ..
Variables
Variable은 BehaviorSubject를 래핑하고 이들의 현재값을 State로 가집니다. 이 때, 현재값은 value 프로퍼티로 알 수 있습니다.
value 프로퍼티를 Variable의 새로운 요소로 가지기 위해선 일반적인 subject나 observable과는 다른 방법으로 추가해야 합니다. 즉, onNext(_:)를 사용할 수 없습니다.
또 하나의 특성은 에러가 발생하지 않을 것임을 보증하기 때문에 .error 이벤트를 variable에 추가할 수 없습니다.
variable은 할당 해제되었을 때 자동적으로 완료되기 때문에 수동적으로 .completed를 할 필요 또한 없습니다.
코드로 살펴봅시다 . . !
example(of: "Variable") {
// 1
// 초기값 가지는 variable 생성
let variable = Variable("Initial value")
let disposeBag = DisposeBag()
// 2
// value 추가
variable.value = "New initial value"
// 3
// 구독하기
variable.asObservable()
.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
/* Prints:
1) New initial value
*/
// 4
variable.value = "1"
// 5
variable.asObservable()
.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
// 6
variable.value = "2"
/* Prints:
1) 1
2) 1
1) 2
2) 2
*/
// .error 나 .completed 이벤트 추가 불가능
}
언제 사용할까 ?
variable은 유동적이므로 observable처럼 구독할 수 있고, subject처럼 새로운 .next 이벤트를 받을 때마다 반응하도록 구독할 수 있습니다.
또한, 업데이트 구독없이 그냥 현재값을 확인하고 싶을 때 일회성으로 적용될 수 있습니다 . . !
'iOS > RxSwift' 카테고리의 다른 글
[RxSwift] Combining Operators (0) | 2022.11.18 |
---|---|
[RxSwift] Transforming Operators (0) | 2022.11.17 |
[RxSwift] Filtering Operators (0) | 2022.10.12 |
[RxSwift] Observable (0) | 2022.10.11 |
[RxSwift] Hello, RxSwift ! (1) | 2022.10.11 |