[RxSwift] dispose를 호출하지 않으면?

Lily·2022년 6월 4일
0

rxSwift🏃🏻‍♀️

목록 보기
3/6

"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이되면서 해제되겠죠?

  1. SecondVC를 dismiss 시켰더니 SecondVC는 메모리 해제되고, 스트림이 complete -> isDisposed 되었습니다.

disposedisposed(by:)를 호출하지 않아도 참조하고 있는 객체가 메모리에서 해제되면 같이 스트림이 종료되는 것을 확인할 수 있었습니다.

  1. 스트림 내부의 subscribe메서드에서 사용한 someClass라는 객체는 해당 블럭을 리턴하면서 메모리가 해제가 되었습니다.

스트림이 dispose되었을 때 내부에서 참조하고 있는 someClass도 해제될 것이라 추측했던 것과는 결과가 다르네요..!


🔬 실험 결과

Observabledispose()disposed(by:)를 호출하지 않아도, 부모 객체(Observable을 참조하는 객체)가 deinit되면 스트림은 dispose되고 리소스가 해제된다.
단, Observable의 클로저에서는 부모 객체를 강하게 참조하면 안된다


👩🏻‍🔬[weak self] 하지 않으면?

그런데 만약 구독안에서 self를 강한 참조하면 어떻게 될까요?

위와 같은 코드에서 [weak self]만 빼고 다시 실행해보겠습니다.

SecondVC가 해제되지 않고, 덩달아 스트림도 dispose되지 않네요....
🚨메모리 누수🚨의 현장이네요

dismiss되어서 해제되어야함에도 불구하고 옵저버블이 self를 참조하고 있기 때문에 강한 순환 참조로 인한 메모리 누수가 발생되네요.

꼭!! [weak self] 또는 withUnretained 를 사용해서 강한 순환 참조를 방지해야겠네요!


정리

강한 순환 참조 상태가 아니라면 dispose를 호출하지 않아도 옵저버블은 dispose되긴 한다만,
좀 더 복잡한 설계에선 각 옵저버블의 참조관계를 일일이 다 파악하기도 어려우므로 컴파일러가 경고하는대로 dispose를 호출해주어야겠다!고 다짐하게된 실험이었습니다.

profile
i🍎S 개발을 합니다

0개의 댓글