안녕하세요.
이번 포스트는 Combine에서 Cancellable과 Subscription에 대해 공부한 개념을 정리하려고 합니다.
1. What is Cancellable and AnyCancellable ?
컴바인 사용할 때 커스텀 subscriber 구현도 하긴 하지만?! 저는 sink(receiveCompletion:receiveValue:), sink(receiveValue:)를 많이 사용합니다. 물론 @Publihsed를 사용할 때 assign(to:on:)도 사용합니다. 이 세 개 operator의 공통적인 특징은 AnyCancellable을 반환하는 것입니다. AnyCancellable은 Cancellable 프로토콜을 채택하는 final class입니다.
Cancellable은 cancel()를 갖고 있습니다. Timers, network access 등의 기능을 수행하는 custom publisher에게 cancel()함수를 사용해서 downstream subscriber에게 publish를 중지할 수 있습니다. 한번 cancel()이 호출되면 더 이상 특정 subscriber에게 publish를 할 수 없게 됩니다. 또한 할당된 자원을 해제하는 함수입니다.
error handling #4 Combine포스트에서 cancel()을 본 적 있습니다. 바로 에러가 던져질 때 입니다. 마찬가지로 더 이상 subscriber에게 value를 줄 수 없습니다.
빠밤! cancellable object를 type-erase 한 녀석입니다. type-erase를 하기 때문에 여러 개의 다양한 커스텀 publisher's sink, assign 등 Combine operator를 통해 반환된 AnyCancellable를 한 곳에( ex.. Set<AnyCancellable>) 저장, holding 할 수 있습니다. store(in:) 함수를 통해서 말입니다. (AnyPublihser 또한 type erase가 되기 때문에 다양한 publisher를 merge등의 작업을 할 수 있습니다.+ operator chains를 거치면 Output 타입이 길어집니다. eraseToPublihser 쓰면 간편해집니다.)
cancel()을 호출 한다는 의미는 publihser, subscriber를 연결하는 subscription을 취소한다는 의미와 같습니다. subscription을 cancel 한다는 의미는 publhiser's Output을 subscriber에게 전달하는 것을 취소한다는 의미입니다. 그리고 위에서 설명에 나오듯 sink 등의 클로저를 통해 제공된 type-erasing cancellable(== AnyCancellable)은 cancel closure를 실행합니다.
AnyCancellable타입은 deinit될 때 cancel()을 호출합니다. 이는 아래 작성한 self question의 좋은 답 중 하나입니다. AnyCancellable instance을 holding 하고 있는 이유는 이들이 deinit 될 때 자동으로 cancel 하기 때문입니다.
2. Why hold AnyCancellable instance ?
저는 여기서 왜?라는 의문점이 들었습니다. "왜 굳이 AnyCancellable 타입을 변수에 저장하거나 store(in:)를 사용해서 관리해야 할까?" 그 해답을 알기 위해선 ARC와 Deinitialization의 관계를 알아야 합니다. deinitialized는 instance가 deallocated 되기 전에 호출되는 것입니다. deinitalized 되기 위해선 instance를 생성해 strong reference를 유지해야 합니다. 즉, AnyCancellable의 참조 객체를 유지하고 있어야 합니다.
sink(receiveCompletion:receiveValue:), assign(to:on:)을 사용할 때 반환된 AnyCancellable 타입을 변수에 저장 또는 store(int:)을 통해 저장하지 않으면 subscription은 곧바로 cancel 시킵니다. AnyCancellable의 strong instance가 없기 때문입니다. AnyCancelable의 object를 유지해야 에러에 의한 or send(completion:) or instance가 deinit 되기 전까지 subscription을 통해 subscriber에게 value를 전달할 수 있습니다.
3. Self question
저는 그래서 거의 publisehr와 subsciber를 연결한 subscription을 type erase를 통해 AnyCancellable 타입의 집합에 저장합니다. 이때 의문점이 있습니다. MVVM 패턴을 사용한 앱에서는 많은 컴바인 Publisher를 선언하고 Subscriber와 subscription을 저장함으로 AnyCancellable을 유지함으로 subscriber에게 연결하고 값을 지속적으로 처리할 수 있게 되는데요. 더 이상 publish를 원치 않을 때 subscription을 .cancel()하는 방법. 특정 Controller가 종료될 때 deinit을 통해 subscriptions를 전부 취소하는 방법은 delegate로 사용된 publisher가 있을 경우 좀 애매하다는 생각이 들었는데요. Swift에서는 subscription의 메모리를 어떻게 관리할지 궁금했습니다.
결론적으로 Combine의 Cancellabel 덕분에 특별하게 메모리를 관리하지 않아도 된다고 합니다. 기본적으로는 VC의 dismiss등을 통해 특정 publisher ojbect가 메모리에서 해제될 때 subscriptions: Set<AnyCancellable> 또한 자동적으로 해제 됩니다. Publisher와 subscriber가 연결된 후 subscription을 Set, array 등의 컬랙션으로 프로퍼티 선언하고 store(in:)을 통해 subscriptions 컬랙션에 저장할 경우 property가 memory에서 release 되고 deinit 될 때 자동적으로 cancel 되고 release 된다고 합니다. subscriber의 클로저는 강력한 캡처보단 [weak self]를 사용해야 ARC가 잘 작동할 수 있습니다. Bind를 쉽게 할 수 있다는 장점이 있습니다.
그러나 그럼에도 화면을 벗어났을 때 subscription들이 해제가 되는지에 대한 검사를 해야합니다. tableView, collectionView에서 reusable cell의 경우 직접 cancel하지 않으면 매번 바인딩이 됩니다...
4. Subscription
subscriber가 publisher를 구독할 때 publisher는 receive(subscription:)을 호출하며 subscription을 생성합니다. publihser는 subscription을 통해 Subscriber에게 값을 전달합니다. 반대로 subscriber는 publihser에게 subscription을 통해 demand를 요청(request(_:))할 수도 있습니다. 이에 따라 subscription은 값을 subscriber에게 emit 합니다(subscriber's receive(_:) 메서드 호출).
Subscription은 Cancellable 프로토콜을 채택하고 있습니다. Subscriber는 subscription을 받습니다. Subscription이 cancel 된다면 이때 할당 되었던 여러 resource를 해제합니다. 그리고 subscription의 cancel은 단 한번 할 수 있습니다.
긴 글 읽어주셔서 감사합니다.
내용 중 틀린 부분이 있을 수 있습니다. 댓글로 알려주신다면 정말 감사합니다!!
참고 자료 :
https://developer.apple.com/documentation/combine/anycancellable/cancel()
https://developer.apple.com/documentation/combine/cancellable/
https://developer.apple.com/documentation/combine/anycancellable
https://www.donnywals.com/what-exactly-is-a-combine-anycancellable/
https://developer.apple.com/documentation/combine/anycancellable/store(in:)-6cr9i
https://www.kodeco.com/books/combine-asynchronous-programming-with-swift