Retain cycles
Closures are non-escaping by default
- This means there is no need to put in
[weak self]
even when usingself
. As soon as the closure is finished, it will clean up after itself and there is no possibility that something else will have an unexpected reference as the closure is non-escaping. .forEach
is an example of a closure, and it is non-escaping- You can always put in
[weak self]
to be safe, but it's not required. And probably good practice to really think about why you are putting it there, rather than just scattering the whole code base with unnecessary[weak self]
- This article is super helpful. Basically - put
deinit
everywhere to debug and see if things are being retained unexpectedly. - If a closure references a function, I think in most instances, you don't need
[weak self]
. Even if the function then accesses variables. Another helpful article about the general concept of when you have a retain cycle vs not.
This article talks specifically about timers. I implemented what it said would cause a retain cycle, then tried to catch the retain cycle using
leaks
and alsodeinit
. But I couldn't catch it out :( So now I'm not too sure how to catch the retain cycle.
import UIKit
class RootVC {
var settingsVCWeak: SettingsVC {
let vc = SettingsVC()
vc.didModifySettings = { [weak self] in
self?.updateView()
}
return vc
}
var settingsVCStrong: SettingsVC {
let vc = SettingsVC()
vc.didModifySettings = {
self.updateView()
}
return vc
}
init() {
print("init root")
}
deinit {
print("deinit root vc")
}
func updateView() {
print("update view")
}
}
class SettingsVC {
var didModifySettings: (() -> Void)?
init() {
print("init settingsVc")
}
deinit {
print("deinit settingsvc")
}
}
var root: RootVC? = RootVC()
//var testWeak: SettingsVC? = root?.settingsVCWeak
//var testStrong: SettingsVC? = root?.settingsVCStrong
root = nil
//testWeak = nil
//testStrong = nil
Only @escaping
closures have potential issues with retain cycles
- The only time a retain cycle should be created is if the closure STORES a value
- If it's just accessing a function, but nothing is stored, then there shouldn't be a retain cycle
- You can always put in
[weak self]
to be safe, but not a big deal
For the animate case, where we have:
open class func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void)
UIView.animate(withDuration: 0.3,
delay: .zero,
options: .curveEaseOut,
animations: {
self.view.layoutIfNeeded()
})
Typically developers won't both with [weak self]
in this case, even though animations
is an @escaping
function.
This is because the closure is completed and disposed of after the animation. Typically the animation is so quick (0.3 seconds in this case), that there's very little chance of a retain cycle.
However if you have a longer animation, yes, this could be an issue.
So I think for safety, it should be animations: { [weak self] in self?.view.layoutIfNeeded() }
Check if you have a memory leak
This article is great. Tells you how to use leaks
in the command line to see if you have a memory leak.
guard let self = self else { return }
- This makes a strong reference to
self
inside the rest of the closure - Use this to make life easier on yourself when coding, so you don't have to handle the
nil
- However note if you have nested closures after the
guard
statement, then these nested closures will hold a strong reference toself
. So you will need to re-declare[weak self]
again in those nested closures. - It's not a requirement, but ideally if you are going to declare
weak self
with nested closures, you may as well declare it on the overarching closure, rather than just the closure you think needs it. Then everything is safe.
if let self = self
- This only makes a strong reference to
self
inside theif
statement - Outside the
if
statement, otherself
references will be weak
Autoclosure - take a look at this concept