본문 바로가기

iOS공통

[RxSwift] self순환 참조에 대해

 

안녕하세요 주니오스입니다.

 

오늘은 RxSwift에서 평소에 습관처럼 사용했던 동작에 대해 한번 자세하게 다루어 보려고합니다.

 

self 약한참조 패턴

 

옵저버블에 대한 구독을 클래스타입 내부에서 진행하고, 내부 프로퍼티를 이벤트를 수신함으로써

 

수정하고 싶은 경우 위와 같은 패턴을 사용하여 순환참조를 막습니다.

 

변경되는 부분이 많아지는 경우 self를 많이 사용해야 하기 때문에 매우 귀찮은 작업입니다.

 

우선 self를 캡쳐해야 하는 이유에 대해 알아보겠습니다.

 

명시적 Self를 요구하는 이유

 

클로저나 매서드나 모두 함수인데 우리는 매서드에서 프로퍼티를 수정하는 경우 self를 사용하지 않는 다는 것을 알고 있습니다.

 

매서드는 implicit self

 

본론부터 말씀드리면, subscribe에 전달된 클로자는 궁극적으로 탈출클로져에 전달되게 됩니다.

 

따라서 함수블록이 끝난 이후에도 self에 대한 참조가 끊어지지 않을 수 있습니다.

 

여기서 중요한건 참조가 끊어지지 않을 수 있다는 것입니다.

 

Swift는 해당 상황을 메모리 누수의 위험성이 높다고 판단합니다.

 

따라서 self를 명시적으로 캡쳐할 것을 컴파일 타임에 알려주는 것입니다.

 

명시적 self캡쳐를 요구한다.

 

매서드도 클로저이긴하지만 인스턴스를 통해서 참조가 이뤄짐으로 self를 강하게 참조하여도 메모리 누수 문제가 발생하지 않습니다.

 

RxSwift의 클로저

self를 강하게 참조하는 Relay가 생성되고 해제되는 과정을 살펴보겠습니다.

self를 강하게 참조

 

우선 subscribe함수를 호출하면 아래와 같이 옵저버를 생성합니다.

 

옵저버 생성

 

subscribe에 넘겨준 클로저는 onNext매개변수로 전달됩니다.

AnonymousObserver

 

생성자에 전달하는 클로저가 탈출클로저인 것을 확인할 수 있습니다.

 

 

이렇게 생성된 옵저버는 PublishRelay에 구독됩니다.

 

PublishRelay는 내부적으로 PublishSubject를 사용함으로 실질적으로 subject에 구독됩니다.

 

※ PublishRelay는 PublishSubject와 달리 에러가 발생하지 않는다는 차이점이 있다.

 

다음으로 옵저버의 on함수가 observers배열에 참조된다.

subject의 옵저버블에 참조된다.

 

자 여기까지의 참조과정을 다이어그램으로 나타내면 다음과 같다.

 

여기까지가 구독과정이다.

 

구독과정에서 생성된 Disposable인스턴스는 DisposeBag의 내부 베열에 저장된다.

DisposeBag insert코드

 

여기까지의 참조과정을 그림으로 나타내면 다음과 같습니다.

 

 

살작 복잡하긴 하지만 해당 부분에서 주목해야할 부분은 순환참조가 발생하는 구간입니다.

 

 

DisposeBag이 가지고 있는 Disposable이 뭔가 해당 고리를 끊어 주지 않을까? 라는 기대 있습니다만.

 

DisposeBag에 코드를 살펴보면 dispose시 단순히 보유하고 있던 Disposable에 대한 참조만 제거합니다.

 

DisposeBag 내부 코드

 

따라서 순환참조를 끊지 못합니다. 심지어 DisposeBag의 dispose함수는 DisposeBag이 deinit되는 경우에만 호출됨으로, self가 강한참조되는 상황에서는 우선 자동으로 호출되지 못합니다.

 

아래코드는 PublishSubjectdispose함수입니다.

PublishSubject dispose함수

 

자신이 보유한 옵저버들(self를 강하게 참조하고 있는 클로저를 포함)에 대한 참조를 끊습니다.

 

따라서 self를 강하게 참조하고 싶은 경우, self를 deinit하기전 명시적으로 dispose를 호출시킴으로써 메모리 누수를 막을 수 있습니다.

 

혹은 명시적으로 클로져 캡쳐 영역에 weak self 혹은 unowned self를 사용하여 순환참조를 해결할 수 있습니다.

 

Driver

Driver는 이벤트를 수신만할 수 있는 옵저버이고 메인쓰레드에서 이벤트를 수신한다.

 

아래코드를 살펴보자

 

아래코드의 publishRelay가 이벤트를 방출하면, 해당 이벤트가 publishRelay2로 전달된다.

drive

 

해당 코드는 drvie가 publishRelay(self의 프로퍼티)를 강하게 참조한다고 볼 수 있다.

 

하지만 순환참조가 발생하지않는다 왜그런걸까?

 

왜냐하면 self에 대한 참조가 아닌 self의 프로퍼티인 publishRelay2에 대한 강한참조이기 때문이다.

 

간단한 말인데 조금 햇갈렸었다. 😅