Reactive X 문서는 여기!
그리고 github의 Getting Started 페이지도 참고했습니다.
이해가 미숙해 틀린 부분이 있을 수 있습니다..
Observable
Observable은 뭘까
사전적 의미로 봤을 때는 '관측 가능한'이라는 뜻을 가지고 있다. 이걸 개발 쪽으로 가져오면 어떤 해석을 할 수 있을까? 어떤 값 또는 이벤트의 변화를 관찰/추적한다는 뜻으로 이해할 수 있을 것 같다. 또한 이벤트는 여러 곳에서 동시다발적으로 일어날 수 있으니 관측하고자 하는 값을 비동기적으로 체크할 수 있어야 할 것 같고. 무언가의 상태 변화를 Observer들이 계속 관측할 수 있도록 하는 것이 Observable이 아닐까?
reactivex.io에서 Observable 문서 첫 문단에 이렇게 설명하고 있다.
옵저버는 Observable을 구독한다. Obseravable이 배출하는 하나 또는 연속된 항목에 옵저버는 반응한다. 이러한 패턴은 동시성 연산을 가능하게 한다. 그 이유는 Observable이 객체를 배출할 때까지 기다릴 필요 없이 어떤 객체가 배출되면 그 시점을 감시하는 관찰자를 옵저버 안에 두고 그 관찰자를 통해 배출 알림을 받으면 되기 때문이다.
오호 Observable은 이벤트를 계속 배출하고, 옵저버는 Observable을 구독해 두면 계속 이벤트를 받아볼 수 있는 것이구나. Notification에 이벤트 등록하는 거랑 좀 비슷해 보인다.
Marble Diagram 이해하는 법
Observable에 대해 계속 알아보기 전에 Marble 다이어그램을 보는 것부터 간단히 짚고 넘어가 보자. RxSwift 또는 ReactiveX를 이해할 때 Marble 다이어그램을 볼 줄 알면 도움이 된다. Marble 다이어그램을 알고 있으면 추후에 보게 될 Operator가 어떤 식으로 동작하는지도 확인할 수 있다. (RxMarbles 페이지)
-------→ : 가로로 길게 뻗은 화살표는 Observable의 타임라인.
⚫️ 🟡 🔵 🔴 : Observable로부터 방출된 아이템(이벤트)들
| : Observable이 종료됨을 나타냄
terminates normally: 정상 종료
terminates with error: 에러로 인한 종료 (|가 아닌 x로 나옴)
infinite sequence: 타임라인이 종료되지 않고 계속 동작하는 것
Event의 생김새 구경하기
Marble diagram을 확인해 봤을 때 Observable은 세 가지의 이벤트를 발생시킨다.
1. item을 배출함
2. 에러를 배출함
3. Observable이 종료됨
RxSwift의 Event 이벤트는 세 가지의 case를 가지고 있다.
/// Sequence grammar:
/// **next\* (error | completed)**
@frozen public enum Event<Element> {
/// Next element is produced.
case next(Element)
/// Sequence terminated with an error.
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
그렇다면 Observable을 구독하는 옵저버들은 뭘 해야 할까. 우선 Observable을 구독하고, 그다음 위 세 개의 이벤트를 처리할 수 있는 코드가 필요하겠지? 옵저버의 코드를 한 번 살펴보자.
만약 어떤 API의 dwonload를 옵저빙 해야 한다고 할 때, 아래와 같이 생각해 볼 수 있다.
FileDownloadAPI.download(fileURL: "http://www....")
.subscribe(onNext: { data in
// 데이터 조각을 받아서 처리할 로직을 onNext에 구현
},
onError: { error in
// 다운로드 error이 들어오는 경우에 대한 처리를 onError에 구현
},
onCompleted: {
작업이 모두 완료된 후에 해야할 일을 onCompleted에 구현
})
원하는 부분을 .subscribe한 뒤 next, error, completed에 대한 부분을 각각 구현하면 된다.
Disposable
subscribe 메서드 정의 부분을 보면 Disposable을 return 한다고 나와있는데, 대체 Disposable은 뭘까?
일단 구글님이 disposable은 '처분할 수 있는'이라는 뜻이라고 알려줬다. 그렇다는 건 뭔가 쓰다가 없애버릴 수 있다는 뜻 같은데, 문서를 찾아 읽어보자.
옵저버가 구독을 terminate 할 수 있는 방법. 시퀀스를 완료하고 다음 아이템을 계산하기 위해 할당된 모든 리소스를 메모리에서 해제하고 싶을 때 dispose 한다.
오호라? 그럼 옵저버가 더 이상 이벤트를 받고 싶지 않을 때 dispose를 하면 된다는 거군? 코드를 한 번 보자.
let scheduler = SerialDispatchQueueScheduler(qos: .default)
let subscription = Observable<Int>.interval(.milliseconds(300), scheduler: scheduler)
.subscribe { event in
print(event)
}
Thread.sleep(forTimeInterval: 2.0)
subscription.dispose()
일단 subscribe의 반환형이 Disposable이 subscription에 저장됐고, 특정 옵저버를 dispose 하기 위해 subscription.dispose()를 호출했다.
그런데 https://github.com/ReactiveX/RxSwift/blob/main/Documentation/GettingStarted.md#disposing 이 링크에서는 예제 코드로 이걸 주고 이렇게 말한다.
Note that you usually do not want to manually call dispose; this is only an educational example. Calling disposemanually is usually a bad code smell. There are better ways to dispose of subscriptions such as DisposeBag, the takeUntil operator, or some other mechanism.
subscribe 했던 Observable을 직접 dispose 메서드를 불러서 해제하는 건 bad code smell이니까 DisposeBag이나 takeUntil 오퍼레이터, 아니면 다른 mechanism을 사용하는 게 더 나아~
그렇다면 dispose가 더 이상 구독이 필요하지 않은 옵저빙을 해제한다는 걸 알았으니 DisposeBag을 알아보자.
DisposeBag
Dispose bags are used to return ARC like behavior to RX.
Dispose Bag은 Rx에서 ARC처럼 동작한다? ARC라면 Reference Count를 통해 인스턴스의 메모리 해제를 관리하는 것이 아닌가! 그렇다는 건 DisposeBag도 옵저빙이 필요 없어지면 메모리에서 해제시켜 버리는 걸 말하는 것 같다.
예제 코드를 확인해 보니 하나의 클래스 내에서 여러 개의 subscribe가 발생할 수 있는데, 이 subscribe 했던 옵저버들의 dispose를 DisposeBag에 담아서 관리한다. subscribe 메서드의 반환형이 뭐였죠? 바로 Disposable.
Disposable의 extension func에는 disposed(by:) 메서드가 있고, 해당 메서드 내에서는 파라미터로 넘어온 bag에 Disposable을 추가해 주는 작업을 수행한다.
let disposeBag = DisposeBag()
let scheduler = SerialDispatchQueueScheduler(qos: .default)
let subscription = Observable<Int>.interval(.milliseconds(300), scheduler: scheduler)
.subscribe { event in
print(event)
}
.dispose(by: disposeBag)
Thread.sleep(forTimeInterval: 2.0)
요런 식으로 클래서 내에 선언해 둔 DisposeBag에 dispose 할 옵저버를 담아두면 메모리에서 클래스가 사라질 때 DisposeBag이 초기화된다고 한다.
DisposeBag 클래스 내부를 살짝 구경해 보자.
아까 Disposable 익스텐션에 있던 bag.insert()의 코드 구조다. 코드 내에 disposables에 disposable을 추가하는 코드가 보인다.
disposables는 당연히 [Disposable]의 형태이고,
deinit에서는 self.dispose()를 호출한다. dispose 코드 내에서는
disposables의 Disposable을 모두 dispose 시키게 된다.
정리해 보면 Rx에서는 Observable이 내뱉는 이벤트 (next, error, complete)를 subscribe를 통해 구독할 수 있고, 또 dispose를 통해 해제시킬 수 있으며 dispose() 메서드를 직접 호출하기보다는 DisposeBag에 담아두면 deinit 될 때 가지고 있던 옵저버들이 알아서 dispose가 되니까 DisposableBag을 애용하세요~ 정도로 마무리할 수 있을 것 같다.
화면상으로 따져보면 api 호출이 되고 있는 도중에 화면이 사라져서 api 콜을 중단해야 하는 경우가 있는 경우 프로퍼티로 disposebag을 선언해두면 뷰 클래스가 deinit 될 때 disposebag도 값이 사라질 테니 api 콜도 중단 되는 것..!
번외
subscribe의 onDisposed
모든 유형의 시퀀스가 종료될 때 호출되는 코드로 dispose 될 때만 불리는 게 아니라 complete, error의 상황에서도 불린다.
error와 completed의 내부에서도 disposable.dispose()가 불려서 그런 듯.
take(until:)