"Disposable의 dispose()
를 호출하지 않으면 정말 메모리 누수가 일어날까?"라는 궁금증에서 시작된 실험을 정리한 글입니다.👩🏻🔬 저처럼 궁금해하시는 분들이 혹~시나~ 계신다면 궁금증을 해결하는데 도움이 되었으면 좋겠네요😊
rxSwift를 공부하며 subscribe
를 한뒤엔 dispose()
를 호출하거나, disposed(by)
를 호출해서 리소스를 해제하고, 메모리 누수를 방지해야한다고 배웠습니다.
그리고 배운대로 실천하던 도중...
Observable도 클래스이고 참조타입이니 참조하고 있던 객체가 메모리에서 해제되면 RC가 0이되면서 해제되지 않을까? 라는 궁금증이 생겼습니다. 그렇다면 굳이 dispose
를 호출하지 않아도 리소스가 정리될 것이니깐요.
(Observable을 subscribe하면 Disposable타입이 리턴되지만, Disposable은 프로토콜이고 Disposable의
그래서 간단하게 실험을 진행해본 결과를 정리해보려고 합니다!
dispose
를 호출하지 않으면?아주 간단한 두개의 뷰컨트롤러를 만들었습니다. Next page
를 누르면 SecondViewController를 present합니다.
SecondVC의 Button
을 누르면 "Hello World😊"를 표시합니다.
button
의 tap이벤트를 시퀀스로 생성하는 disposable
을 만들어주었고, 상태를 관찰하기 위해 debug
를 찍어주었습니다.
그리고 SecondViewController의 메모리해제를 관찰하기 위해 deinit
을, disposable
이 사용하는 자원의 메모리 해제를 관찰하기 위해 SomeClass
라는 타입을 구현했습니다.
import UIKit
import RxSwift
import RxCocoa
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.bind()
}
@IBOutlet weak var label: UILabel!
@IBOutlet weak var button: UIButton!
let disposeBag = DisposeBag()
func bind() {
let disposable = button.rx.tap
.debug()
.subscribe(onNext: { [weak self] in
self?.label.text = "Hello, World😊"
let someClass = SomeClass()
})
}
deinit {
print("SecondVC 해제됩니다👋")
}
}
class SomeClass {
init() {
print("someClass 생성됩니다✨")
}
deinit {
print("Resource 해제됩니다👋")
}
}
제가 생각한대로라면 SecondViewController
가 dismiss되면서 메모리에서 해제되고, disposable을 참조하고 있던 객체가 nil이 되니 disposable도 RC가 0이되면서 해제되겠죠?
SecondVC
를 dismiss 시켰더니 SecondVC
는 메모리 해제되고, 스트림이 complete
-> isDisposed
되었습니다.dispose
나 disposed(by:)
를 호출하지 않아도 참조하고 있는 객체가 메모리에서 해제되면 같이 스트림이 종료되는 것을 확인할 수 있었습니다.
subscribe
메서드에서 사용한 someClass
라는 객체는 해당 블럭을 리턴하면서 메모리가 해제가 되었습니다.스트림이 dispose되었을 때 내부에서 참조하고 있는 someClass
도 해제될 것이라 추측했던 것과는 결과가 다르네요..!
🔬 실험 결과
Observable
의dispose()
나disposed(by:)
를 호출하지 않아도, 부모 객체(Observable
을 참조하는 객체)가 deinit되면 스트림은 dispose되고 리소스가 해제된다.
단,Observable
의 클로저에서는 부모 객체를 강하게 참조하면 안된다
[weak self]
하지 않으면?그런데 만약 구독안에서 self
를 강한 참조하면 어떻게 될까요?
위와 같은 코드에서 [weak self]
만 빼고 다시 실행해보겠습니다.
SecondVC
가 해제되지 않고, 덩달아 스트림도 dispose되지 않네요....
🚨메모리 누수🚨의 현장이네요
dismiss되어서 해제되어야함에도 불구하고 옵저버블이 self
를 참조하고 있기 때문에 강한 순환 참조로 인한 메모리 누수가 발생되네요.
꼭!!
[weak self]
또는withUnretained
를 사용해서 강한 순환 참조를 방지해야겠네요!
강한 순환 참조 상태가 아니라면 dispose
를 호출하지 않아도 옵저버블은 dispose되긴 한다만,
좀 더 복잡한 설계에선 각 옵저버블의 참조관계를 일일이 다 파악하기도 어려우므로 컴파일러가 경고하는대로 dispose
를 호출해주어야겠다!고 다짐하게된 실험이었습니다.