Hi -
I'm working with Combine and SwiftUI. Recently I've come across an issue with Combine and assign(to published: inout Published<Self.Output>.Publisher)
which is producing some results that are confusing to me and I'm hoping someone in the community can help explain if I doing something wrong or if there is possible a memory leak.
FYI I came across this topic Does ‘assign(to:)’ produce memory leaks? which is similar but different (as you'll see further on).
Here is a very simplified version of what I'm doing:
import Combine
class ChildViewModel {
@Published var done: Void = ()
init(){
print("init ChildViewModel - \(Unmanaged.passUnretained(self).toOpaque())")
}
deinit { print("deinit ChildViewModel - \(Unmanaged.passUnretained(self).toOpaque())") }
}
class ParentViewModel {
@Published var childViewModel: ChildViewModel? = nil
private var cancellableSet = Set<AnyCancellable>()
init(useAssign: Bool) {
if useAssign {
$childViewModel
.compactMap { $0 }
.eraseToAnyPublisher()
.flatMap { $0.$done }
.map { _ in nil }
.assign(to: &$childViewModel)
} else {
$childViewModel
.compactMap { $0 }
.eraseToAnyPublisher()
.flatMap { $0.$done }
.map { _ in nil }
.sink { [weak self] in self?.childViewModel = $0 }
.store(in: &cancellableSet)
}
}
deinit { print("deinit ParentViewModel") }
}
print("ParentViewModel test - use `assign`")
var viewModel: ParentViewModel? = ParentViewModel(useAssign: true)
viewModel?.childViewModel = ChildViewModel()
viewModel?.childViewModel = ChildViewModel()
print("`viewModel` set to nil")
viewModel = nil
print()
print("ParentViewModel test - use `sink` & `store`")
viewModel = ParentViewModel(useAssign: false)
viewModel?.childViewModel = ChildViewModel()
viewModel?.childViewModel = ChildViewModel()
print("`viewModel` set to nil")
viewModel = nil
What I'm trying to say in the code is that I have a class ParentViewModel
. It has an optional member childViewModel
. When ever childViewModel
has a value I'd like to listen to a publishing member of ChildViewModel
called done
. If done
produces a value I'de like to clear childViewModel
by setting its value to nil
.
running this code produces the following results
ParentViewModel test - use `assign`
init ChildViewModel - 0x0000600000a18360
init ChildViewModel - 0x0000600000a279a0
deinit ChildViewModel - 0x0000600000a18360
`viewModel` set to nil
deinit ParentViewModel
ParentViewModel test - use `sink` & `store`
init ChildViewModel - 0x0000600000a09900
init ChildViewModel - 0x0000600000a097a0
deinit ChildViewModel - 0x0000600000a09900
`viewModel` set to nil
deinit ParentViewModel
deinit ChildViewModel - 0x0000600000a097a0
as you can see, if I use assign(to:)
then the latest ChildViewModel
doesn't get deinit
ed. If I use sink
and store
, both ChildViewModel
s get deist
ed properly. It appears that only if I change ParentViewModel
deinit
as follows
deinit {
childViewModel = nil
print("deinit ParentViewModel")
}
does the current value of childViewModel
get properly deinit
ed when I use 'assign'.
Am I wrong in expecting that the assign(to:)
version of this code should clear away childViewModel
when parentViewModel
is set to nil
just as the sink
and store
version does?
Any insight would be much appreciated. Cheers!