Memory leak using MutableProperty.map
FranDepascuali opened this issue · comments
Hey there! First of all, thanks for this awesome framework. It really shaped the way I code and I prefer it vastly over other options.
Today I had a memory leak I wanted to share:
class Foo {
fileprivate var planes: [UUID: Plane] = [:]
fileprivate(set) var plane: ReactiveSwift.Property<Plane?>!
fileprivate let _currentPlaneIdentifier: MutableProperty<UUID?>
init() {
...
plane = _currentPlaneIdentifier.map { UUID in UUID.flatMap { [unowned self] in self.planes[$0] }}
}
}
It's difficult to spot at a first sight, but the fix is to change
plane = _currentPlaneIdentifier.map { UUID in UUID.flatMap { [unowned self] in self.planes[$0] }}
to
plane = _currentPlaneIdentifier.map { [unowned self] UUID in UUID.flatMap { self.planes[$0] }}
As I understand, there is a memory leak in the first because the mutable property retains self via the closure (but as the closure isn't executed, self is not deallocated)? This example confuses me, if someone can provide some feedback it would be great.
Thanks!
(NOTE that the internal flatMap is optional's flatMap)
This is a gotcha of ARC that applies outside of ReactiveSwift as well (including in Objective-C). Here's something similar to your example using NotificationCenter
:
// (1)
self.observation = NotificationCenter.default.addObserver(forName: Foo.notification, object: nil, queue: nil) { notification in
let uuid = notification.userInfo?[Foo.uuidKey] as? UUID
// (2)
uuid.flatMap { [unowned self] in
completionHandler(self.planes[$0])
}
}
The main thing to note here is that when the [unowned self]
capture list is evaluated, self
is in the context of the first closure, (1). This creates a strong reference to self
in the outer closure, in order to make the unowned reference in the inner closure, (2). This is effectively what the capture list does:
unowned let self = self // on the rhs, self is strong
uuid.flatMap { self.planes[$0] }
Hopefully that clears up where the strong reference is coming from.