Disposables

이숭인·2021년 6월 28일
0

Mastering RxSwift

목록 보기
4/7

Disposables 란?

  • Observable이 전달하는 메소드(Event)가 아니다.
  • 사용했었던 subscribe 메소드에서 파라미터로 클로저를 전달받게 되면 Observable과 관련된 모든 리소스가 해제된 후에 호출되는 녀석이다.
subscribe(onNext: ((Int) -> Void)?,
            onError: ((Error) -> Void)?,
            onCompleted:(() -> Void)?,
            onDisposed: (() -> Void)?
            )

이전 게시글에서 아래와 같이 두가지 방식으로 subscribe을 호출했었는데

// # 1
Observable.from([1,2,3])
    .subscribe { element in
    print("Next",element)
} onError: { error in
    print("Error",error)
} onCompleted: {
    print("Completed")
} onDisposed: {
    print("Disposed")
}

// # 2
Observable.from([1,2,3])
    .subscribe{
        print($0)
    }

이 둘을 실행 시켜보면 아래와 같은 결과가 출력된다.

결과를 비교해보면,

  • #1 에서는 Disposed가 호출되었고
  • #2 에서는 Disposed가 호출되지 않았다.

그렇다면, Disposed 메소드는 Observable과 관련된 모든 리소스가 해제된 후에 호출된다고 했는데 그럼 #2 에서는 Observable에 관련된 모든 리소스가 해제되지 않은건가?

결론적으로 #1, #2 둘 다 해제됬다. ㅋㅅㅋ

  • Observable이 Completed 메소드나 Error 이벤트로 종료되었다면 관련된 리소스가 자동으로 해제되기 때문.
    그렇다면 의문점.

Disposed 메소드는 Observable의 모든 리소스가 해제될때 호출되는 녀석인데 왜 Completed, Error 이벤트로 종료가 된 후에 리소스가 해제되는 시점에 호출되지 않았을까?

  • 이유는 Disposed는 Observable이 전달하는 메소드(Event)가 아니기 때문.
    (Observable이 전달하는 메소드는 Next, Completed, Error 3가지)
  • #1 에서 Disposed 메소드가 호출되었던 이유는 onDisposed 파라미터로 클로저를 전달해 두었기 때문에 리소스가 해제되는 시점에 자동으로 호출된 것.
    (#2 에서는 그런 작업이 없었으니까 Disposed 메소드가 호출되지 않은거임.)

음.. 그래서 왜 #1과 #2 처럼 두가지 방법이 있을까?

아마.. 내 생각엔 Observable 에서 리소스가 해제되는 시점에 어떤 작업을 하고싶다
#1 처럼 작성하고, 그런경우가 아니라면 #2처럼 작성하면 될 것 같다.

어 그럼 Disposables를 꼭 몰라도 되는건가?

문제(..) 는 아니지만 위에서 그랬지? 정상적으로 Observable이 종료되면(Completed or Error 이벤트로) 자동으로 관련 모든 리소스가 자동으로 해제된다고..

근데 애플 공식문서를 보면 Completed 나 Error로 종료되는 경우에도

  • 리소스가 자동으로 해제되는 것 보다는 직접 해제하는걸 권장함.

(사실 그래서 Disposed를 알아야하는거임 ㅋㅋㅋ.)

Disposable은

  • 실행 취소
  • 리소스 해제

에 사용되기때문

그럼 어떤 방식으로 리소스를 해제하고 실행 취소를 시킬까?

코드로 살펴보자.

리소스를 해제하는 경우

let subscription1 = Observable.from([1,2,3])
    .subscribe { element in
    print("Next",element)
} onError: { error in
    print("Error",error)
} onCompleted: {
    print("Completed")
} onDisposed: {
    print("Disposed")
}

subscription1.dispose()

위와 같이 dispose() 메소드를 사용해 리소스를 해제할 수 있다.

하지만 공식문서에서는 이런식으로 리소스 해제하는걸 권장하지 않는다.

왜 권장하지 않는지 실행을 취소하는 경우의 코드를 설명하면서 정리해보자.

실행을 취소하는 경우

// #1
let subscription2 = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .subscribe { element in
        print("Next",element)
    } onError: { error in
        print("Error",error)
    } onCompleted: {
        print("Completed")
    } onDisposed: {
        print("Disposed")
    }
// #2
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    subscription2.dispose()
}

+코드에 대해 간단히 설명하자면,

  • #1 은 1씩 증가하는 정수를 1초 간격으로 이벤트를 무한으로 방출한다.
  • #2 는 이벤트를 무한으로 방출하는 #1 의 실행을 멈출 필요가 있기 때문에 dispose()메소드로 직접 중지시켰다.

실행 결과

자, 뭐가 문제라서 권장하지 않는걸까?
결과를 한번 보자.

Next 0 -> Next 1 -> Next 2 -> Disposed 순으로 이벤트가 발생했다.

어 이상한점이 있네?
바로 Observable이 마지막 이벤트까지 처리됬을때 마지막에 호출되는 Completed 가 보이지 않는다.
왜냐하면 dispose()메소드로 Observable을 강제 종료했기 때문!

이런식으로 강제 종료하게 된다면 Completed 가 호출되지 않는 등의 문제로 인해 다른 방식으로 리소스를 해제하는것을 권장한다.

DisposeBag

이거다. DisposeBag을 이용해서 리소스를 해제해야한다.

자, 만들어보자.

var bag = DisposeBag()

Observable.from([1,2,3])
    .subscribe{
        print($0)
    }
    .disposed(by: bag) // #1
    
bag = DisposeBag()  // #2
  • #1 처럼 파라미터로 disposeBag을 전달하면 subscribe가 리턴하는 disposable이 DisposeBag에 추가된다.

위의 방법처럼 disposeBag에 여러 observable의 disposable을 저장할 수 있고, disposeBag에 추가된 disposable은 disposeBag이 해지되는 시점에 함께 해지된다.

그렇다면 disposeBag의 리소스를 해제하기 위해선 어떻게 해야할까?

간단히 2가지정도 방법이 있다.

  • #2 처럼 새로운 DisposeBag을 할당하거나

  • 처음부터 #2의 변수를 optional 변수로 만들어 nil을 넣어주면 된다.

결국!
이렇게 Observable이 전달하는 이벤트가 종료될 때, 모든 리소스를 해제하기 위해서는 위와 같은 방식으로 disposeBag을 이용하는 것을 권장한다. !

음..

쓰다보니 엄청 길어졌는데 그냥 간단히 요약하자면 !

  • Observer가 구독하는 Observable의 이벤트가 종료되는 시점에 리소스 해제하기 위해선 아래 코드처럼 disposed(by:) 메소드를 통해 DisposeBag에 저장하고, DisposeBag의 리소스 해제를 함으로서 모든 리소스를 해제한다!

이런느낌

Observable.from([1,2,3])
    .subscribe{
        print($0)
    }
    .disposed(by: bag) // #1
profile
iOS Developer

0개의 댓글