Skip to content

Commit e69ef98

Browse files
committed
Add ViewModelCancellable to manage cancellation/clear logic
1 parent f5ad567 commit e69ef98

File tree

3 files changed

+42
-10
lines changed

3 files changed

+42
-10
lines changed

KMPObservableViewModelCore/ObservableViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ public final class ObservableViewModel<VM: ViewModel>: ObservableObject, Hashabl
3434
/// The observed `ViewModel`.
3535
public let viewModel: VM
3636

37+
/// Holds a strong reference to the cancellable
38+
private let cancellable: AnyCancellable
39+
3740
internal init(_ viewModel: VM) {
3841
objectWillChange = viewModel.viewModelWillChange
3942
self.viewModel = viewModel
43+
cancellable = ViewModelCancellable.get(for: viewModel)
4044
}
4145

4246
public static func == (lhs: ObservableViewModel<VM>, rhs: ObservableViewModel<VM>) -> Bool {

KMPObservableViewModelCore/ObservableViewModelPublisher.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ public final class ObservableViewModelPublisher: Combine.Publisher, KMPObservabl
1313
public typealias Output = Void
1414
public typealias Failure = Never
1515

16-
internal weak var viewModel: (any ViewModel)?
16+
internal let cancellable = ViewModelCancellable()
1717

1818
private let publisher: ObservableObjectPublisher
1919
private let subscriptionCount: any SubscriptionCount
2020

2121
internal init(_ viewModel: any ViewModel) {
22-
self.viewModel = viewModel
2322
self.publisher = viewModel.objectWillChange
2423
self.subscriptionCount = viewModel.viewModelScope.subscriptionCount
2524
}
@@ -32,14 +31,6 @@ public final class ObservableViewModelPublisher: Combine.Publisher, KMPObservabl
3231
public func send() {
3332
publisher.send()
3433
}
35-
36-
deinit {
37-
guard let viewModel else { return }
38-
if let cancellable = viewModel as? Cancellable {
39-
cancellable.cancel()
40-
}
41-
viewModel.clear()
42-
}
4334
}
4435

4536
/// Subscriber for `ObservableViewModelPublisher` that creates `ObservableViewModelSubscription`s.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// ViewModelCancellable.swift
3+
// KMPObservableViewModel
4+
//
5+
// Created by Rick Clephas on 09/06/2025.
6+
//
7+
8+
import Combine
9+
10+
/// Helper object that provides a weakly cached `Cancellable` for a `ViewModel`.
11+
internal class ViewModelCancellable {
12+
13+
private var didInit = false
14+
private weak var cancellable: AnyCancellable?
15+
16+
private func get(_ viewModel: any ViewModel) -> AnyCancellable {
17+
guard didInit else {
18+
let cancellable = AnyCancellable {
19+
if let cancellable = viewModel as? Cancellable {
20+
cancellable.cancel()
21+
}
22+
viewModel.clear()
23+
}
24+
self.cancellable = cancellable
25+
didInit = true
26+
return cancellable
27+
}
28+
guard let cancellable = self.cancellable else {
29+
fatalError("ObservableViewModel for \(viewModel) has been deallocated")
30+
}
31+
return cancellable
32+
}
33+
34+
static func get(for viewModel: any ViewModel) -> AnyCancellable {
35+
viewModel.viewModelWillChange.cancellable.get(viewModel)
36+
}
37+
}

0 commit comments

Comments
 (0)