Disposable
Disposable은 subscribe 중인(= 구독중인) stream을 원하는 시기에 처리할 수 있도록 도와준다.
모든 Observable은 Disposable을 반환한다.
이를 통해서 stream을 종료하고 실행되고 있던 시퀀스를 모두 종료한다.
Observable의 next 이벤트 emit이 끝나면, (= 모든 이벤트가 방출되면)
completed에서 disposed로 시퀀스가 정상적으로 종료된다.
하지만, interval 코드 등과 같이 별다른 조건 없이 무한하게 emit 될 수 있는 상황에서는 disposed 되지 않는다.
클래스의 메모리가 해제되는 시점, 즉 deinit이 되는 시점에서 disposable이 되지만, 만약 (루트 뷰가 변하지 않는 상황에서의) RootViewController일 경우에는 메모리에서 클래스가 해제되지 않는다.
이 때에는 Root 뷰의 모든 이벤트가 발생되고 나서야 Disposed되거나 아니면 Disposed가 되지 않을 수도 있다.
➡️ 이럴 경우에는 직접 관리를 해주어야 하며, 원하는 시점에 dispose 메서드를 호출해서 리소스를 정리할 수 있다.
그러나, 만약 위와 같은 성격의 코드가 여러 개가 있다면, 여러 개의 이벤트에 대한 구독을 하고 있다면, 모든 구독을 하나씩 종료하는 것은 번거로운 일이다.
그렇기 때문에 DisposeBag의 인스턴스를 초기화하는 과정을 통해서 한번에 리소스를 정리할 수 있다.
Observable VS Subject
Observable은 이벤트를 생성하고 전달을 한다.
여기서 포인트는 전달만 한다는 것이다.
새로운 값을 Observable에 추가할 수 없다.
- 사용자의 입력 등에 따라 실시간으로 데이터의 변화를 감지하여 표현하고
- 변경된 데이터를 subscribe를 통해
- emit할 수 있는 객체가 필요하다.
Subject는 Observable과 Observer 모두를 담당하고 있으므로
✅ 이벤트를 전달할 수도 있고
✅ 전달받은 이벤트에 대해서 처리할 수도 있다.
또한, Observable을 여러 번 subscribe 할 경우 독립적인 실행을 갖지만
subject의 경우는 여러 번 subscribe를 해도 그 실행이 내부적으로 공유된다.
.. 이게 무슨 말인고 .. ? 하니 .. 아래의 코드를 보자.
let observableInt = Observable<Int>.create { observer in
observer.onNext(Int.random(in: 1...100))
return Disposables.create()
}
observableInt
.subscribe { value in
print("observableInt \(value)")
}
.disposed(by: disposeBag)
observableInt
.subscribe { value in
print("observableInt \(value)")
}
.disposed(by: disposeBag)
let subjectInt = BehaviorSubject(value: 0)
subjectInt.onNext(Int.random(in: 1...100))
subjectInt
.subscribe { value in
print("subjectInt \(value)")
}
.disposed(by: disposeBag)
subjectInt
.subscribe { value in
print("subjectInt \(value)")
}
.disposed(by: disposeBag)
observable의 경우는 독립적인 실행을 갖기 때문에 각 두번의 실행에서 서로 다른 값이 나오지만
subject의 경우는 두 번의 구독 실행에서 모두 같은 값이 나오는 것을 확인할 수 있다.
subject와 observable에 대한 차이는 다른 글에서 좀 더 자세하게 다뤄보자 ..
Subject
Publish Subject
- 초기값이 없는 빈 상태로 시작한다.
- 구독 이후 시점, 즉 subscribe 시점 이후부터 emit되는 이벤트를 처리할 수 있다. subscribe 이전에 emit된 이벤트들은 모두 무시된다.
- 그러므로 구독 이전에 onNext로 여러번 emit해도 이 때 방출한 이벤트는 모두 무시하는 것이다.
let publish = PublishSubject<Int>()
private func publishSubject() {
publish.onNext(1)
publish.onNext(2)
publish
.subscribe { value in
print("publish - \(value)")
} onError: { error in
print("publish - \(error)")
} onCompleted: {
print("publish completed")
} onDisposed: {
print("publish disposed")
}
.disposed(by: disposeBag)
publish.onNext(3)
publish.onNext(4)
publish.on(.next(5))
publish.onCompleted()
publish.onNext(6)
publish.onNext(7)
}
위의 코드를 실행하면 아래와 같이 나오는 것을 확인할 수 있다.
구독 전, 다시 말해서 .subscribe를 실행하기 전 publish.onNext(1) 코드와 publish.onNext(2) 코드에 대해서는 emit을 무시한 것이다.
Behavior Subject
- Behavior Subject를 생성할 때 초기값은 필수이다.
- 이 점이 Publish Subject와의 차이점이다.
- 초기값이 있기 때문에 이벤트를 받기 전까지 일종의 더미 데이터 등을 보여줘야 하는 경우, 뷰를 채우기에 용이하다.
- Subscribe 이전에 emit한 이벤트가 있다면 가장 최근에 전달된 이벤트 하나를 전달 받을 수 있다.
- Subscribe 이전에 emit한 이벤트가 없다면 초기값을 전달한다.
쉽게 말해서 Behavior Subject는 subscribe 하기 바로 전 이벤트를 전달 받는다.
-> 이 때, 바로 전 이벤트가 있을 수도 있고, 없을 수도 있다.
-> 그러므로 초기값이 필요한 것이다.
let behavior = BehaviorSubject(value: 100)
func behaviorSubject() {
behavior.onNext(1)
behavior.onNext(200)
behavior
.subscribe { value in
print("behavior - \(value)")
} onError: { error in
print("behavior - \(error)")
} onCompleted: {
print("behavior completed")
} onDisposed: {
print("behavior disposed")
}
.disposed(by: disposeBag)
behavior.onNext(3)
behavior.onNext(4)
behavior.on(.next(5))
behavior.onCompleted()
behavior.onNext(6)
behavior.onNext(7)
}
위의 코드를 실행하면 아래의 출력 결과를 얻을 수 있다.
1과 200 모두 emit 했지만 구독 시점 바로 직전인 200에 대해서만 이벤트를 전달받고 있다.
Replay Subject
- bufferSize에 작성이 된 이벤트만큼 메모리에 이벤트를 갖고 있다가 subscribe를 한 직후 한번에 이벤트를 전달한다.
- 이 때 저장된 buffer size만큼을 메모리에서 따로 갖고 있는 것이므로 많은 양을 버퍼로 가질 경우 메모리 부하가 발생할 수 있다.
- 오류가 발생하더라도 메모리에서 보유하고 있는 이벤트를 emit하고 error를 notification한다.
let replay = ReplaySubject<Int>.create(bufferSize: 5)
private func replaySubject() {
replay.onNext(100)
replay.onNext(200)
replay.onNext(300)
replay.onNext(400)
replay.onNext(500)
replay
.subscribe { value in
print("replay - \(value)")
} onError: { error in
print("replay - \(error)")
} onCompleted: {
print("replay completed")
} onDisposed: {
print("replay disposed")
}
.disposed(by: disposeBag)
replay.onNext(3)
replay.onNext(4)
replay.on(.next(5))
replay.onCompleted()
replay.onNext(6)
replay.onNext(7)
}
위의 코드를 실행해보면, 5만큼의 메모리를 보유하고 있는 것이다.
그리고 100부터 500까지의 이벤트를 갖고 있다가 구독한 시점 이후에 한번에 emit하는 것이다.
만약 이벤트는 그대로 두고 buffer size를 3으로 줄이면?
let replay = ReplaySubject<Int>.create(bufferSize: 3)
이렇게 구독 시점 전부터 3개의 이벤트에 대해서만 전달을 받고 있다.
Async Subject
이 친구는 그렇게 자주 사용하는 친구는 아니라고 하니까 .. 가볍게 알아두기 .. ~
- 위의 세가지 subject는 구독한 이후, 이벤트가 전달되면 즉시 이벤트를 전달한다.
- 하지만 AsyncSubject는 subscribe를 했더라도 completed 이벤트가 전달 되기 전까지 어떤 이벤트도 전달하지 않는다.
- 만약 completed 이벤트가 전달되었다면 가장 최근 시점에 전달된 next 이벤트 하나를 함께 전달한다.
let async = AsyncSubject<Int>()
private func asyncSubject() {
`async`.onNext(100)
`async`.onNext(200)
`async`.onNext(300)
`async`.onNext(400)
`async`.onNext(500)
`async`
.subscribe { value in
print("async - \(value)")
} onError: { error in
print("async - \(error)")
} onCompleted: {
print("async completed")
} onDisposed: {
print("async disposed")
}
.disposed(by: disposeBag)
`async`.onNext(3)
`async`.onNext(4)
`async`.on(.next(5))
`async`.onCompleted()
`async`.onNext(6)
`async`.onNext(7)
}
위의 코드를 실행하면
이런 결과가 나온다. completed가 되기 직전의 이벤트를 전달한다. (subscribe 직전이 아니라 completed!!)
'Swift > RxSwift' 카테고리의 다른 글
[🌱SeSAC] Observable/Observer, Subject, Relay (0) | 2022.10.26 |
---|---|
[🔥Rx뿌셔] Subject VS Observable (0) | 2022.10.26 |
[🔥Rx뿌셔] Disposable (0) | 2022.10.25 |
[🔥Rx뿌셔] Subscribe (0) | 2022.10.25 |
[🔥Rx뿌셔] Observable (2) | 2022.10.24 |