I use Combine in viewModels to update the views. But if I store the AnyCancellable objects into a set of AnyCancellable, the deinit method is never called. I use the deinit to cancel all cancellables objects.
struct View1: View {
@ObservedObject var viewModel:ViewTextModel = ViewTextModel()
@Injected var appActions:AppActions
var body: some View {
VStack {
Text(self.viewModel.viewText)
Button(action: {
self.appActions.goToView2()
}) {
Text("Go to view \(self.viewModel.viewText)")
}
}
}
}
class ViewTextModel: ObservableObject {
@Published var viewText: String
private var cancellables = Set<AnyCancellable>()
init(state:AppState) {
// initial state
viewText = "view \(state.view)"
// updated state
state.$view.removeDuplicates().map{ "view \($0)"}.assign(to: \.viewText, on: self).store(in: &cancellables)
}
deinit {
cancellables.forEach { $0.cancel() }
}
}
Each time the view is rebuilt, a new viewmodel is instantiated but the old one is not destroyed. viewText attribute is updated on each instance with state.$view.removeDuplicates().map{ "view \($0)"}.assign(to: \.viewText, on: self).store(in: &cancellables)
If I don't store the cancellable object in the set, deinit is called but viewText is not updated if the state's changed for the current view.
Do you have an idea of how to manage the update of the state without multiplying the instances of the viewmodel ?
Thanks
You could use
sinkinstead ofassign:But I question the need for Combine here at all. Just use a computed property:
UPDATE
If your deployment target is iOS 14 (or macOS 11) or later:
Because you are storing to an
@Published, you can use theassign(to:)operator instead. It manages the subscription for you without returning anAnyCancellable.