본문 바로가기

Swift/RxSwift

[🔥Rx뿌셔] Disposable

728x90

dispose .. disposeBag .. 뭣이 중헌디 .. 

Observable을 구독할 때 필수로 사용해야 하는 dispose와 disposeBag에 대해서 알아보자 !!!

 

왜 알아야 하는데?!

다 피가 되고 .. 살이 된다 ..

 

 

Rx로 코드를 작성하다 보면, 방출된 이벤트를 처리할 때 dispose를 하지 않을 시 아래 이미지와 같은 경고 메시지가 발생하는 것을 볼 수 있다.

물론 그냥 .. 경고 메시지니까 .. 실행에는 큰 문제가 없을 것이라고 판단하여 넘어갈 수 있지만 .. 그렇지 않다 !!!

 

경고 메시지를 보게 되면 

우리가 어떤 Observable을 .subscribe를 통해 구독하게 되면 

해당 Observable은 Disposable을 반환한다.

 

위의 이미지에서 볼 수 있는 것처럼 subscribe 메서드는 Disposable을 반환값으로 한다. 

반환을 하는데 이에 대한 처리를 하지 않기 때문에 경고가 나타나는 것이다.

 

 

Disposable

dispose라는 개념은 Rx에서 구독을 취소하는 것과 같다.

 

어떤 Observable이 성공적으로 모든 요소들을 방출하고 onCompleted까지 방출이 되었다면, 더 이상 Observable을 관찰할 이유가, 구독할 이유가 없다.

(Observable에서 onCompleted를 통해서 더 이상 방출할 이벤트가 없다면 말해주었기 때문이다.)

 

그렇다면, 구독자는 그 Observable에 대한 구독를 취소할 필요가 있다.

그렇지 않다면, 메모리 누수와 같은 이슈로 이어질 수 있기 때문이다.

 

 

이럴 때 !!!

.dispose() 연산자를 통해 구독을 취소한다.

그렇다면, 의도한 대로 구독을 취소할 수 있는 것이다 .

 


DisposeBag

Red five diamonds in my bag
If you wanna see it, dance to a beat like that

ㅈㅅ ..

 

하지만 위와 같이 .dispose()로 매번 구독 취소하는 것은 효과적이지 않다.

왜냐하면 각 시퀀스를 따로따로 구독 취소하는 것에 따른 리스크도 있기도 하고 RxSwift에서 이미 이보다 더 훌륭한 방법을 제공하고 있기 때문이다. 

 

그 방법이 바로 disposeBag을 사용하는 방법이다. 

Bag이라는 키워드에서 알 수 있는 것처럼, 이름 그대로 dispose를 할 수 있도록 담아두는 곳이다. disposable한 것들을 담아두는 것이다.  

disposeBag에 disposable한 것들을 담아 두고 마지막, 한번에 구독을 취소하는 것이다.

 

 

만약 아래와 같은 코드를 생각해보자.

let subscriber1 = observable.subscribe(onNext: { _ in
	// do something
})

let subscriber2 = observable.subscribe(onNext: { _ in
	// do something
})

let subscriber3 = observable.subscribe(onNext: { _ in
	// do something
})

let subscriber4 = observable.subscribe(onNext: { _ in	
	// do something
})

하나의 Observable에 대해서 subscribe1, subscribe2, subscribe3, subscribe4가 구독을 하고 있다.

 

이 때 모든 구독자들이 Observable의 구독을 끊고 싶다면?

 

#dispose를 통해서 구독을 취소할 수 있다.

subscriber1.dispose()
subscriber2.dispose()
subscriber3.dispose()
subscriber4.dispose()

이렇게 코드를 작성하면 모든 구독자와 Observable과의 관계는 끊어진다.

 

#하지만 동시에 작업할 수 있는데, 이게 바로 disposeBag이다. 

(구독을 취소할) 모든 구독자들을 disposeBag에 담고 한번에 버리는 것이다.

 

코드로 나타내면 아래와 같다. 먼저 전역 변수로 disposeBag을 선언한다. 

let disposeBag = DisposeBag()

 

그리고 Observable을 .subscribe()할 때 .disposed(by:) 연산자를 이어서 호출한다.

여기서 by 파라미터에 전역 변수로 선언한 disposeBag을 그대로 넣어주면 된다.

 

observable.subscribe(onNext: { _ in
	// do something
}).dispose(by: disposeBag)

이렇게 작성하면 해당 ViewController가 해제될 때, 즉 deinit될 때 disposeBag이라는 변수도 해제되면서 그 안에 들어 있는 disposable 요소들이 한번에 해제되는 것이다. 

 

 

만약 특정 disposeBag에 대해서 임의로 해제를 해야 하는 경우 등에는 nil을 넣어서 해제할 수도 있다.

disposeBag = nil

이 때에는 전역변수로 선언시, disposeBag이 옵셔널 타입이어야 한다.

 

또는, 전역 변수로 선언시 var로 선언한 다음 새로운 DisposeBag()을 할당하는 방법도 있다.

 

 


왜 줄줄 새는 것인데?!

구독 취소를 하지 않으면 왜 메모리 누수가 발생하는데!?!

 

예를 들어서 화면이 두개가 있다고 가정해보자.

MainViewController와 SubViewController가 있고, 이때 메인은 루트 뷰에 해당하며 MainViewController에서 present를 통해  SubViewController로 화면이 전환 되는 상황이라고 가정해보자.

 

SubViewController는 1초에 한번씩 숫자를 출력하는 Observable이 있다.

그리고 그 Observable을 구독한 구독자가 있다. 

 

 

이 상황에서 SubViewController에서 dismiss를 하면 SubViewController 화면에 대한 deinit이 일어나게 된다. 

그리고 여기서 dispose를 한 것과 하지 않은 것에 차이가 발생하게 된다.

 

🔴 disposeBag을 사용하지 않은 경우

"0" -> "1" -> "2" -> 해제 -> "4" -> "5" -> "6" .. 

무한반복된다. 

 

🟢 disposeBag을 사용한 경우 

"0" -> "1" -> "2" -> 해제 

 

Observable을 구독하고 실행되는 것은 모두 비동기로 일어나기 때문에 이런 결과가 벌어지게 되는 것이다. 

 

만약, SubViewController가 많은 양의 api를 호출하는 Observable로서 관리하고 있다면? .. 대참사다 .. 참사 ..

그렇기 때문에 무조건 dispose 작업이 필요하다.

 

🔥 정리
Observable을 subscribe 할 때는 무조건 dispose(by: ) 를 호출해서 disposeBag에 넣어서 관리하도록 하자!
특수한 경우에는 바로 .dispose() 를 이용해 구독해제를 해도 좋다.

 

'Swift > RxSwift' 카테고리의 다른 글

[🔥Rx뿌셔] Subject VS Observable  (0) 2022.10.26
[🌱SeSAC] Disposable, Observable, Subject  (0) 2022.10.26
[🔥Rx뿌셔] Subscribe  (0) 2022.10.25
[🔥Rx뿌셔] Observable  (2) 2022.10.24
[🔥Rx뿌셔] Intro  (1) 2022.10.24