[RxSwift] subscribe의 동작원리 이해하기
RxSwift의 구독은 어떻게 이루어질까?
지난 포스트에서는 옵저버블이 어떻게 생성되는지 create 메서드를 따라가보면서 정의해보았습니다.
그리고 오늘은 subscribe를 하면 어떤 일이 일어나는지 확인해보겠습니다! 지난 포스트를 읽으면 더 쉽게 이해할 수 있으니 확인해보세요!
아래 예제를 통해서 subscribe의 동작을 보려고해요. 그럼 시작하겠습니다!
let observable = Observable<Int>.create { observer in
observer.onNext(1)
observer.onCompleted()
return Disposables.create()
}
let disposable = observable.subscribe(
onNext: { data in print(data) },
onError: { error in print(error) },
onCompleted: { print("completed") },
onDisposed: { print("dispose done") }
)
ObservableType.subscribe 뜯어보기
먼저 시작점인 subscribe부터 확인해보죠.
Observable의 subscribe를 보면 인자가 observer 타입인데요, 저희는 subscribe에서 observer를 전달한 적이 없습니다. onNext:, onError: 이런 파리미터에 클로저를 전달했었죠.
저런 파라미터를 받는 subscribe는 이번에도 역시 ObservableType에 정의되어 있습니다.
ObservableType은 네 개의 클로저를 받는 subscribe를 구현하고 있습니다. 내부를 살펴보면 조금 특이한데요, 내부에서 AnonymousObserver를 생성하고 있어요.
subscribe한다고 구독자가 되는게 아니라 알고보니 내부에서 구독자를 만들어주고 있었던거죠..
한가지 눈에 띄는건 error와 completed 이벤트는 우리가 subscribe를 하면서 정의한 클로저를 실행하고 dispose까지 실행해주게 됩니다!
이제 AnonymousObserver의 내부도 확인해봐야겠죠? 일단 AnonymousObserver의 생성자에 우리가 전달한 클로저가 각 이벤트에 맞게 호출되는 swtch 문이 클로저로 전달되고 있다는 것을 기억합시다!
AnonymousObserver의 구현은 생각보다 단순합니다! 인자로 전달된 클로저(switch 문을 포함한 클로저)를 eventHandler에 저장한 뒤에 생성자는 종료되고, 나중에 onCore라는 메서드가 호출되면 이 클로저를 실행해줍니다.
AnonymousObserver가 생성되었으니까 다시 돌아갑시다!
이제 observer를 만들었고, disposable도 만들어져 있습니다. 그러면 이 subscribe 메서드가 반환하는건 Disposables.create의 결과인데, 인자로 두 값이 들어가고 있습니다.
disposable은 그렇다치는데 self.asObservable().subscribe(observer)가 너무 뜬금없어보이죠..
어떤 결과가 나오는지 확인해봅시다.
AnonymousObservable.asObservable
self.asObservable()에서 self가 누구인지 정의해봅시다. 우리가 호출한 subscribe 메서드는 지난 포스트에서 만들었던 AnonymousObservable이겠죠.
그럼 AnonymousObservable의 asObservable 메서드를 확인해보겠습니다.
asObservable을 찾기 위해 AnonymousObservable 부터 따라가보면,
AnonymousObservable -> Producer -> Observable -> ObservableType 순으로 부모 클래스를 따라갔을 때 asObservable 메서드를 찾을 수 있었습니다.
구현은 위와 같은데요, Observable.create를 호출하면서 { oberver in self.subscribe(observer) }를 전달합니다. 지난 포스트에서 create는 인자로 전달받은 클로저를 subscribeHandler에 저장하는 AnonymousObservable을 반환했습니다.
이번에도 동일하게 AnonymousObservable을 반환합니다. 그리고 subscribeHandler에는 self.subscribe(observer)가 저장되어 있게 됩니다.
다시 subscribe로 돌아갈게요!
백 투 subscribe
우리가 asObservable을 보게된게 self.asObservable().subscribe(observer)를 알기 위해서였죠?
asObservable의 결과로는 AnonymousObservable이 반환되었다는 것까지 보았습니다. 이 내부의 subscribeHandler에는 { oberver in self.subscribe(observer) } 가 들어있구요.
헷갈리죠? 그림으로 확인해볼게요.
지금 우리는 두 종류의 AnonymousObservable을 가지고 있어요. 첫번째 옵저버블은 제일 처음에 만들었던 옵저버블이고 두번째 옵저버블은 첫번째 옵저버블에 대해 subscribe를 호출하면서 만들었던 옵저버블입니다.
여기서 self.asObservable까지 진행했을 때 만들어진 옵저버블인거죠. 이후를 위해서 한가지 기억해야하는건 두 번째 옵저버블이 가지고 있는 subscribeHandler의 self가 첫번째 옵저버블이라는 것입니다!
첫번째 옵저버블에서 만들어진 클로저니까 self를 캡쳐해서 가지고 들어가게 되었습니다. 이게 이해가 안되면 앞으로도 계속 이해가 안되니까 꼭 이해하고 넘어가주세요!!
subscribe를 다시 호출..!
아직 이 코드가 끝나지 않았습니다..! 위에서 만들었던 파란색 옵저버블에 대한 subscribe를 호출하게 됩니다. 이번에는 아까와 인자가 다르네요. subscribe의 인자로 이전에 만들었던 AnonymousObserver가 전달되고 있습니다.
이번에는 AnonymousObserver를 인자로 받는 subscribe의 정의를 찾아볼게요!
AnonymousObservable의 상위 클래스인 Producer에서 subscribe를 찾을 수 있었습니다.
여기서는 SinkDisposer라는 객체를 만들어주고 Producer의 run메서드를 실행한 뒤에, 그 결과를 SinkDisposer 인스턴스에 넣어주고 있습니다.
가장 주요해보이는 run 메서드를 일단 확인해볼게요. run의 인자로는 subscribe를 호출하며 전달된 AnonymousObserver와 방금 만든 SinkDisposer가 전달됩니다.
Producer.run
producer의 run 메서드는 추상클래스만을 제공하고 있어요. 아마도 실제 구현은 AnonymousObservable에 있겠죠. 찾아갑시다!
run 메서드에서 하는 일은 AnonymousObservableSink 인스턴스를 만들고, 그 인스턴스의 run 메서드를 호출하는 것입니다.
더 보기전에 AnonymousObservableSink로 갑시다. run의 인자로 받은 observer와 cancel을 그대로 넘겨주고 있어요.
여기서 전달된 observer가 뭐였는지 슬슬 잘 모르겠죠..? 이번에도 그림으로 남겨두겠습니다!
이 녀석입니다ㅎㅎ eventHandler 안에 첫 subscribe에서 만들었던 이벤트에 대한 클로저가 저장되어 있습니다.
좀 정리가 되시나요? AnonymousObservableSink로 이동하겠습니다!
AnonymousObservableSink
생성자로 전달된 observer와 cancel은 그대로 부모클래스의 생성자에 전달이 됩니다. Sink의 생성자를 빠르게 보고 다시 올게요.
Sink의 생성자에서는 크게 하는 일은 없고 전달받은 observer와 cancel을 그대로 프로퍼티에 저장해주고 있어요.
이제 AnonymousObservableSink의 인스턴스는 생성이 되었고, 원래 보던 producer의 run으로 다시 돌아갈게요.
백 투 run
sink 변수에는 AnonymousObservableSink의 인스턴스가 저장되었고 sink의 run 메서드를 확인해봅시다.
run의 인자로 self가 전달이되는데요, self는 subscribe를 호출했던 파란색 AnonymousObservable입니다.
이 녀석이요!
AnonymousObservableSink.run
run은 Parent라는 타입을 파라미터로 가지는데요, Parent는 그냥 AnonymousObservable의 타입 앨리어스입니다. 그리고 run 안에서는 Parent가 가지고 있는 subscribeHandler를 실행하고 그 결과를 반환합니다. subscribeHandler는
이 친구이겠구요, 이 클로저의 인자로 self를 AnyObserver 타입으로 변환한 인스턴스가 들어가게 됩니다. self는 AnonymousObservableSink인데, 이 인스턴스는 아까 부모 클래스인 Sink에 observer를 전달했던 그 인스턴스 입니다.
이 친구요! 이제 AnyObserver의 생성자를 확인해서 인스턴스가 어떻게 생성되는지 확인해보겠습니다.
생성자에서는 전달받은 ObserverType 인스턴스의 on 메서드를 자신의 EventHandler로 저장하고 있어요.
on 메서드는 여기에 있고, 내부에서 forwardOn을 부르고 있는데 아마도 Sink에 있는 forwardOn을 부르고 있겠죠? 그리고 Sink에는 eventHandler를 가진 초록색 옵저버가 저장되어있구요. 뭔가 좀 감이 오지 않나요..? 일단 forwardOn은 실제로 호출했을 때 확인해보고 run의 subscribeHandler를 실행해봅시다.
이게 subscribeHander 였고, 이 클로저를 실행하면, self.subscribe(observer)가 실행이 됩니다. 맞아요, subscribe가 한 번 더 실행됩니다.
Subscribe 어게인..
self.subscribe(observer)를 실행하려고 하는데, self는 누구일까요?
이 그림이 기억나시나요? 저 클로저 안에 있는 self는 노란색 옵저버블을 캡쳐해서 들고 들어간 것이었어요. 따라서 노란색 옵저버블의 subscribe가 호출되는 것이고, 그 인자로 방금 AnyObserver로 변환한
이 옵저버가 전달이 되는 것이죠.
옵저버이니까 Producer의 subscribe가 다시 실행되겠군요. 그럼 지금까지 거쳐온 과정들을 다시 거치게 됩니다.
여기서 run이 불리면서 인자로 초록색 옵저버가 전달이 될거구요, 아까는 self가 파란색 옵저버블이었는데, 이번에는 노란색 옵저버블이겠죠!
run 안에서는 아까 봤던 것 처럼
새로운 ObservableSink도 하나 만들게 되고,
다시 ObservableSink의 run을 호출합니다.
자, 그럼 이번에 전달된 parent는 누구일까요? subscirbe를 호출한 인스턴스이니까 노란색 AnonymousObservable이 parent 로 전달됩니다.
이 옵저버블의 subscribeHandler가 호출이 되는 것이죠. 그리고 인자로 전달되는 옵저버에는
이 옵저버가 전달이 됩니다.
드디어... 이벤트의 방출과 이벤트 처리 클로저가 만나기 직전입니다..
SubscribeHandler
이제 실행을 해보겠습니다.
먼저 인자로 전달된 observer의 onNext를 호출하면서 인자로 1을 전달합니다. observer는 AnyObserver로 변환했던 AnonymousObserver였죠?
onNext를 찾아봅시다.
onNext는 AnyObserver의 부모클래스인 ObserverType에 정의되어 있어요. onNext는 자기자신의 on을 호출하면서 next 이벤트를 전달하고, 전달받은 값도 함께 전달하죠.
아까 AnyObserver로 변환할 때 AnonymousObserver의 on 메서드를 EventHandler로 저장했었어요. 그리고 AnyObserver의 on 메서드는 이 EventHandler를 호출하는 것이기 때문에 결국에는 AnonymousObserver에 있던 on 메서드가 호출되는 것과 같습니다.
AnonymousObserver는 on을 구현하고 있지 않아서, 부모 클래스인 ObserverBase를 찾아가볼게요.
ObserverBase의 on은 onCore를 호출하는데, onCore는 AnonymousObserver에서 오버라이딩해서 구현하고 있어요ㅋㅋ 왔다갔다... 어쨌든 onCore를 보면 결국엔 자신의 eventHandler를 호출해주게 됩니다.
여기에 저장된 eventHandler가 호출되고, 이벤트가 전달됩니다. 이벤트는 .next(1) 이겠죠!
그럼 switch 문에 따라서 .next가 케이스 매칭이되고, onNext?(value)를 호출하는데, 이 함수는 아까 AnonymousObserver를 만들 때 캡처된 함수에요.
여길 보면 subscribe의 인자로 전달했던 onNext를 AnonymousObserver를 만들면서 그대로 캡처하고 있는거죠.
그렇다면 onNext?(value)는 subscribe의 인자로 전달했던 onNext를 호출하는 것이 되고
let disposable = observable.subscribe(
onNext: { data in print(data) },
onError: { error in print(error) },
onCompleted: { print("completed") },
onDisposed: { print("dispose done") }
)
data in print(data) 가 호출되면서 1이 출력됩니다.
아직 subscribeHandler의 코드가 안끝났죠? 다음은 onCompleted 가 전달되고, 같은 과정을 거쳐 "completed" 가 출력됩니다. 그리고 eventHandler에는 dispose를 호출하는 코드가 담겨있으니 dispose까지 호출됩니다.
마지막으로 Disposable.create()를 호출하면서 비어있는 Disposable을 생성하여 반환하고 subscribeHandler는 종료됩니다.
반환하기
subscribeHandler를 마치고 반환된 Disposable은 연속적으로 반환되어서 subscription에 저장되고
run은 sink에는 AnonymousObservableSink의 인스턴스, subscription에는 subscribeHandler를 모두 실행하고 반환된 Disposable을 저장해 튜플로 반환합니다.
이렇게 반환된 sink와 subscription은 disposer 내부에 저장되고, 이 disposer가 다시 반환됩니다.
subscribe의 반환타입을 보면 Disposable인데요, SinkDisposer는 Disposable을 채택하고 있고,
dispose는 sink와 subscription의 dispose를 모두 불러주도록 구현되어 있어요. 우리가 disposeBag을 통해 부르는 dispose가 이 메서드이죠.
subscribe가 두 번 불렸기 때문에 이 과정을 한번 더 거쳐서 반환되면
여기로 다시 돌아오게 됩니다.
최종적으로 반환되는 Disposable에는 두 종류의 disposable이 들어가는데요, 첫번째는 자기자신에 대한 subscribe를 만들면서 옵저버를 등록했던 Disposable이고, 두번째는 subscribe안에서 만들었던 disposable(실제 rx에서는 더 복잡해요, 이 프로젝트에서는 dispose에 클로저를 허용하지 않기 때문에 빈 disposable입니다)이 됩니다.
마무리
긴 여정을 지나서 subscribe가 어떻게 이루어지는지 확인해보았습니다. RxSwift를 사용할 때는 뭔가 구독을 시작하면 이벤트가 짠! 하고 전달되는 것 같았는데, 내부적으로 이렇게 콜스택을 쌓아가면서 처리하는지 몰랐어요.. 이런 컨셉을 떠올리고 만들어낸 알엑스 팀도 대단한 것 같고. 시간은 진짜 오래 걸렸지만 꽤나 재밌었습니다!
위의 모든 과정을 주석으로 적어서 레포로 정리했어요! 관심있으시면 오셔서 확인해보세요! 읽어주셔서 감사합니다!
https://github.com/jeonyeohun/Understanding-RxSwift-Internals