2024. 12. 5. 15:40ㆍFramework, Library
이번 글에서는 Combine의 Cancellable과 AnyCancellable을 뽀개보고자 합니다. (어떻게 보면, Publisher Subscriber보다 더 중요한..)
애플의 표현처럼 설명하자면, Combine은 어메이징하고 원더플하고 아주 유익한 cancellation이란 메커니즘을 만들어두었다고 합니다.
이 cancellation의 내용은 Cancellable이라는 프로토콜에 어마무시하게 정의되어있고,
얘를 사용할 때는 리얼리리얼리 컨비넌스한 AnyCancellable로 Cancellable의 cancellation 기능을 활용하시면 된다는 그런 이야기..인데요.
사실 이 말만 들으면 "엥?" 하면서 무슨 내용인지 하나도 머릿속에 들어오지 않을거라 생각해요.
그래서 헷갈릴 수 있는 개념들을 먼저 확실하게 머릿속에 박아두고 글을 시작해볼까 합니다!
설명도 아래 순서대로 진행할 거고요.오늘 글 출발해 보겠습니다~~💨
cancellation : “Combine에서 구독 관계를 끊는다”는 개념 (Terminate Subscriptions early)
Cancellable : “Cancellation의 수행 기능”을 담고 있는 프로토콜
AnyCancellable : “Cancellable의 구현부”를 작성한 클래스
1. Cancellable
💡 Cancellable : A protocol indicating that an activity or action supports cancellation.
→ Cancellable은 어떤 활동이나 동작의 취소를 지원함을 나타내는 프로토콜이다.
1-1. Cancellable은 활동이나 동작의 취소를 지원한다.
“아니 활동이나 동작의 취소를 지원하는 게 뭐길래.. 그게 그리 대단한 거야?”
cancellable을 직역해 보면 Publisher와 Subscriber의 “구독 관계를 취소 가능하도록” 만든다는 겁니다.
구독 관계가 형성되는 것이 있으면 / 끊어지는 것도 필요한 법!
*비동기 데이터 스트림이 진행 중인데, 만약 사용자가 해당 화면을 벗어나서 더 이상 데이터 스트림을 받아올 필요가 없다면?
**몇 번까지 데이터 스트림을 받다가 특정 횟수 이후부터는 받아올 필요가 없다면? 굳이 구독 관계를 유지할 필요가 없으니! “이런 구독 관계가 언제든지 취소가능해야 한다”는 = “cancellation의 메커니즘”이 Cancellable에 구현되어 있는 거죠.
하지만, Cancellable이 중요한 이유는 이것 말고도 하나 더 있었으니…
1-2. Cancellable과 Swift ARC
바로 메모리 관리 측면에서 Cancellable이 중요한 역할을 한다는 점입니다!
왜냐면 Cancellable이 ARC로 관리되기 때문인데유….
Combine에서 Publisher와 Subscriber는 서로 강한 참조로 연결되어 - Cancellable 객체는 메모리에 계속 남아있게 됩니닷.
이때 불필요한 메모리 낭비와 순환 참조를 방지하는 데 있어 Cancellable이 역할을 하게 됩니다!
이 부분은 아래 AnyCancellable 부분까지 다루고 깊게 들어가 볼게요!
일단은 아 Cancellable이 ARC로 관리되는구낭…구독 관계는 강한참조구낭….그렁가봉다..하고 넘어갑시다!
1-3. Cancellable은 프로토콜이다.
“As with assign, sink will return a cancellable that you can then use to terminate the subscription.”
위의 문장은 WWDC에서 Cancellable을 처음 소개할 때의 표현인데요,
직역하면, assign이나 sink는 cancellable을 return합니다. 라는 뜻이네요.
다르게 말하면, Subscriber들은 cancellable을 return합니다. 라는 뜻이기도 해요.
한번 더 풀어서 말하면, Publisher에 대한 구독이 생성되면 (= subscribe 관계가 형성되면) Cancellable을 return합니다. 라는 뜻으로도 해석할 수 있습니다.
앗!
여기서 착각하면 안됩니다.
Cancellable은 클래스가 아니라, 프로토콜이걸랑요?
그래서 다시 올바르게 말하면, Publisher에 대한 구독이 생성되면 (= subscribe 관계가 형성되면), Cancellable이라는 프로토콜을 준수하는 객체가 return됩니다. 라는 뜻이 정확하다고 볼 수 있겠네요.
결국은 다 같은 말입니다. 이게 무슨 말이냐고요? 코드 고고링~
// let trickNamePublisher = ... <String, Never>
// Subscriber 두 가지 방법~
let canceller1 = trickNamePublisher.assign(to: \\.someProperty, on: someObject)
let canceller2 = trickNamePublisher.sink { trickName in
// ...
}
이 코드는 Subscriber 대표 두 녀석 assign, sink 방식을 각각 사용해서 trickNamePublisher의 데이터 스트림을 구독하는 코드를 만들어준 거죠.
위의 설명에 따르면, 코드에 있는 canceller1과 canceller2에는 모두 Cancellable,
다시 말해 Cacellable 프로토콜을 따르는 어떤 객체가 반환되어 담기게 될 거라는 의미입니다! 헷갈리지 마셔용~
2. AnyCancellable
네네. 그 Cancellable 프로토콜을 따르는 어떤 객체는 바로 AnyCancellable이었습니다.
💡 AnyCancellable : A type-erasing cancellable object that executes a provided closure when canceled.
→ 취소되었을 때 주어진 클로저를 수행하는 type-erasing 된 cancellable 객체이다.
2-1. AnyCancellable은 Type-Erase 되었다.
바로 지난 글이었던 AnyPublisher를 설명하던 부분을 상기시켜보면,
Publisher의 종류가 매우 다양했는데 - 이 다양한 Publisher들을 하나로 묶어주고, 외부로 구현부를 숨기고, 다형성을 준수해주기 위해 AnyPublisher라는 Publisher의 type-erase를 수행하는 객체를 사용했었죠.
AnyCancellable도 마찬가지입니다.
Cancellable 프로토콜을 준수하여 return 하는 녀석들의 구체적인 구현부를 숨기고,
모두 통합하여 구독을 관리할 수 있도록 하는 type-erase 된 객체 AnyCancellable을 일관되게 사용하도록 만들었슴니다아.
그러니까 아래 코드처럼 하나의 Set에 모든 Cancellable들을 담아서 관리할 수 있게 된 거죠….!
private var cancellables = Set<AnyCancellable>()
2-2. 주어진 클로저를 수행한다는 게 무슨 말?
AnyCancellable의 초기화 부분을 보게 되면, 아래와 같이 클로저를 지정하는 것이 보이실 거예요!
이 클로저는 cancel 메서드가 호출될 때 수행한다고 되어있습니다.
즉, 아래와 같이 AnyCancellable을 생성할 때, print문을 클로저 안에 담으면
해당 anyCancellable이라는 객체에 cancel이 수행되면, 클로저 속 print문이 작동한다는 뜻이죠!
let anyCancellable = AnyCancellable {
print("cancel됨!")
}
anyCancellable.cancel() // cancel됨!
3. 그러면 cancel()을 맨날 호출해야한다는겨? 나는 cancel을 쓴 기억이 없는디?
그츄? 저도 그래요.
명시적으로 cancel()을 호출할 수도 있지만, 그거는 너무 귀찮기 때문에….
보통은 아래와 같이 cancellables 혹은 cancelBag이라는 이름의 여러 AnyCancellable들을 담을 수 있는 Set 컬렉션을 만들어두고,
Subscriber의 return값으로 넘어오는 AnyCancellable을
store(in:)라는 Cancellable의 메서드 파라미터로 담는 방식으로 구독 관계를 관리했을 거에욥.
private var cancellables = Set<AnyCancellable>()
examplePublisher.sink { value in
// 어쩌구 코드
}
.store(in: &cancellables)
제가 예전 AnyCancellable을 짧게 설명한 적이 있었는데, 그때는 이렇게 설명한 적이 있었어요.
- Set<AnyCancellable> : 여러 구독을 하나의 Set에 저장해서 관리한다. Set이기 때문에 구독이 중복될 수 없으며, 객체가 만들어진 VC가 메모리에서 해제되면 자동으로 cancel()이 호출되어 모든 구독이 일괄적으로 취소된다.
근데 의문이 생겼어요.
“객체가 만들어진 VC가 메모리에서 해제되면 자동으로 cancel()이 호출되어 모든 구독이 일괄적으로 취소된다.”
이게 어떻게 가능한 것인지. 그래서 OpenCombine 코드를 찾아봤쥬.
4. OpenCombine 코드 뜯어보기! - 메모리에서 해제되면, 자동으로 cancel이 호출된다?
4-1. 초기화
AnyCancellable을 초기화하는 코드는 두 개가 있었어요.
- cancel() 호출 시에 수행할 클로저를 파라미터로 받는 부분
- Cancellable을 채택하고 있는 객체를 파라미터로 받는 부분
정리하면, 이 _cancel이라는 프로퍼티에는 클로저 혹은 / 다른 Cancellable 객체의 cancel 부분이 담기게 되겠네요!
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
public init(_ cancel: @escaping () -> Void) {
_cancel = cancel
}
public init<OtherCancellable: Cancellable>(_ canceller: OtherCancellable) {
_cancel = canceller.cancel
}
...
4-2. cancel()
그리고 예상했던 것처럼 AnyCancellable의
_cancel 프로퍼티 클로저는 cancel() 메서드가 호출될 때 불러오게 되어 있다는 것을 확인할 수 있었습니다.
아 그리고!
_cancel이 바로 nil로 초기화되는 걸 볼 때,
“3-2에서 설명했던 내용의 클로저는 최초 1회만 작동되는구나!” “그 이후에 cancel을 반복 호출하더라도 동작하지는 않겠군!”
같은 점도 확인할 수 있었답니다.
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
...
public func cancel() {
_cancel?()
_cancel = nil
}
4-3. deinit
와우 그리고 진짜 핵심.
“AnyCancellalbe이 deinit 되면, 자동으로 cancel이 호출된다.” 정확하게 말하면, cancel 클로저가 호출된다.
/// An `AnyCancellable` instance automatically calls `cancel()` when deinitialized.
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
...
deinit {
_cancel?()
}
}
그니까 그니까 결론.
보통 이런 구독 관계가 viewDidLoad()에서 호출되고 / 형성되었는데,
만약, 화면이 전환되어 VC가 메모리에서 해제되면, 자동으로 해당 클래스 프로퍼티였던 AnyCancellable deinit 되고, 위 코드의 의해 cancel이 호출되어 불필요한 구독관계가 끊기는 거쥬!
즉, 위에서 말했던 “객체가 만들어진 VC가 메모리에서 해제되면 자동으로 cancel()이 호출되어 모든 구독이 일괄적으로 취소된다.”
부분이 이런 식으로 되어 있던 겁니다!
cancel()을 호출하는 것과, 레퍼런스 카운트 -1 되는 것과는 연관성이 없기 때문에,
명시적으로 cancel()을 호출해서 사용하더라도 메모리 해제를 별도로 체크해야 하는 방법보다 / 프로퍼티로 메모리 해제를 관리 → 메모리 해제 시 자동 cancel() 호출의 흐름을 갖는 이 방법을 사용하는 게 유리하다고 이해하시면 될 것 같숨당~
이제 여러분들은 Combine의 Cancellable 개념에 대해 완벽히 정복하셨습니다. 축하드려요!!!! 끝!