.sink { self.name = $0 } stored in self.cancellables — why does the view model never dealloc, and what is the fix?
This view model is never deallocated, even after its screen is dismissed and nothing else references it.
final class ProfileViewModel {
@Published private(set) var name = ""
private var cancellables = Set<AnyCancellable>()
init(names: AnyPublisher<String, Never>) {
names.sink { value in
self.name = value // strong capture of self
}
.store(in: &cancellables)
}
}
Explain why self never deallocates and fix the retain cycle.
self owns cancellables, which owns the AnyCancellable, whose sink closure captures self strongly — a retain cycle, so self never deallocs. Fix it with a [weak self] capture list, or use assign(to: &$name), which captures no self.
- ✗Capturing
selfstrongly in asinkclosure stored onself - ✗Blaming
store(in:)or@Publishedinstead of the closure capture - ✗Forgetting that
assign(to: &$x)avoids capturingself
- →Why does
assign(to: &$name)avoid the retain cycle? - →When is
[unowned self]unsafe here compared to[weak self]?
self owns cancellables, cancellables owns the AnyCancellable, and the sink closure captures self strongly — a retain cycle: self → cancellables → AnyCancellable → closure → self. Break it with a capture list:
init(names: AnyPublisher<String, Never>) {
names
.sink { [weak self] value in
self?.name = value
}
.store(in: &cancellables)
}
[weak self] (or .assign(to: &$name), which does not capture self at all) removes the strong edge, so the view model deallocates when its screen goes away.