I’m developing a Swift iOS app using MVVM. I noticed yesterday that my ViewModel was being retained by a “Swift closure context.”
I’ve made an example app so you can see the problem for yourselves. It is here: https://www.dropbox.com/s/kn23lptixk25acq/LeakTest.zip
Instructions: Run the app, wait 5 seconds for the BlueViewController to be replaced by the RedViewController and then hit the Debug Memory Graph button. Observe that BlueViewModel is retained.
Do I need to be worried about this kind of retain cycle? Will the retain cycle be broken eventually?
Do I need to be worried about this kind of retain cycle?
Yes.
Will the retain cycle be broken eventually?
No.
For the sake of OOPer and others, here’s a snippet of your code, slightly edited to simplify things:
class BlueViewModel: NSObject {
var updateUI: (() -> Void)!
var titleText: String {
return "..."
}
}
class BlueViewController: UIViewController {
var viewModel: BlueViewModel!
init(viewModel: BlueViewModel) {
self.viewModel = viewModel
super.init(nibName: "BlueViewController", bundle: nil)
viewModel.updateUI = { [weak self] in
self?.titleLabel.text = viewModel.titleText
}
}
… more stuff …
}
You’ve done the weak self dance in
BlueViewController
, so that there’s nothing to stop that getting deallocated. However, you’ve still get a retain cycle between your closure (lines 15 through 17) and the
viewModel
object itself. Specifically, line 16 references the
viewModel
local variable from line 12, not the
viewModel
property from line 10.
There’s a bunch of ways you can fix this. Probably the easiest is to change line 16 to this:
self?.titleLabel.text = self?.viewModel.titleText
At that point the closure is only capturing
self
and thus doesn’t retain the view model directly.
However, I think it would be better to rethink your strategy a bit because the current structure just leaves potential footguns lying around your project. One approach I often use is to have the closure pass a ‘self’ value to the callback, which rules out a whole class of problems like this. For example:
class BlueViewModel: NSObject {
var updateUI: ((BlueViewModel) -> Void)!
…
}
Alas this won’t work in your real project because
BlueViewModel
is a subclass. These days I avoid that by avoiding inheritance, using structs for my model ‘objects’, but that’s a pretty big change of direction.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"