If I have a closure passed to a function like this:
someFunctionWithTrailingClosure { [weak self] in
anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
If I declare self as [weak self]
in someFunctionWithTrailingClosure
's capture list without redeclaring it as weak
again in the capture list of anotherFunctionWithTrailingClosure
self
is already becoming an Optional
type but is it also becoming a weak
reference as well?
Thanks!
In Swift, [weak self] prevents closures from causing memory leaks in your application. This is because when you use [weak self], you tell the compiler to create a weak reference to self. In other words, the ARC can release self from memory when necessary.
Capture lists: The list of values that you want to remain unchanged at the time the closure is created. Capturing values: If you use external values inside your closure, Swift stores them alongside the closure. This way they persist even when the external part of the code no longer exists.
Keep in mind that a closure with a weak capture will treat the captured property as an optional. So if you have a [weak self] in your capture list, you'll likely want to unwrap self in your closure body using guard strongSelf = self else { return } to make sure that self still exists by the time the closure is executed.
Capturing an object or value which is getting used inside a closure is a pretty neat feature. But it might introduce strong retain cycles or reference cycles if not used properly. When working with closures, be mindful about capture list. If any value or object is getting used inside a closure, capture it using unowned or weak.
So if you have a [weak self] in your capture list, you'll likely want to unwrap self in your closure body using guard strongSelf = self else { return } to make sure that self still exists by the time the closure is executed.
In this example, the self-captured as weak and sampleDelegate captured as unowned. As the self is unowned,? Is not required as it assumes that self will be in memory when the closure executed.
The [weak self]
in anotherFunctionWithTrailingClosure
is not needed.
You can empirically test this:
class Experiment {
func someFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}
func anotherFunctionWithTrailingClosure(closure: @escaping () -> Void) {
print("starting", #function)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
closure()
print("finishing", #function)
}
}
func doSomething() {
print(#function)
}
func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
And then:
func performExperiment() {
DispatchQueue.global().async {
let obj = Experiment()
obj.testCompletionHandlers()
// sleep long enough for `anotherFunctionWithTrailingClosure` to start, but not yet call its completion handler
Thread.sleep(forTimeInterval: 1.5)
}
}
If you do this, you will see that doSomething
is never called and that deinit
is called before anotherFunctionWithTrailingClosure
calls its closure.
That having been said, I might still be inclined to use the [weak self]
syntax on anotherFunctionWithTrailingClosure
to make my intent explicit.
TL;DR
Although using [weak self]
once in the outer block is fine (EX1), if you change this reference to strong (e.g. guard let self = self
), you'll need a [weak self]
in the inner block as well (EX3).
Also using [weak self]
only once on the inner block is typically an error (EX2_B). Unfortunately, this is a common mistake to make when refactoring code, and can be hard-to-spot when it is made.
A good rule of thumb is to always use weak
if the object is strong immediately outside of the closure.
Examples that don't retain self
(i.e. typically these are the "good" scenarios):
// EX1
fn { [weak self] in
self?.foo()
}
// EX2
fn { [weak self] in
fn2 {
self?.foo()
}
}
// self is weak inside fn, thus adding an extra `[weak self]` inside fn2 is unnecessary
// EX3
fn { [weak self] in
guard let self = self else { return }
fn2 { [weak self] in
self?.foo()
}
}
Examples that DO retain self
(i.e. typically the "bad" scenarios):
// EX1_B
fn {
self.foo()
}
// fn retains self
// EX2_B
fn {
fn2 { [weak self] in
self.foo()
}
}
// fn retains self (this is a common, hard-to-spot mistake)
// EX3_B
fn { [weak self] in
guard let self = self else { return }
fn2 {
self.foo()
}
}
// fn2 retains self
As Hamish alludes to, there are two main reasons weak
is useful:
In Rob's example, the function is not retaining the closure (beyond dispatch_async which is all but guaranteed to fire the closure at some point in the future), thus you'll never end up with a retain cycle. So using weak
in this case, then, is to prevent #2 from happening.
As Hamish mentions, weak is not actually needed in this example to prevent retain cycles, as there are no retain cycles. weak
, in this case, is used to prevent an object living longer than needed. It depends entirely on your use-case as to when you consider an object living longer than needed. Thus there are times when you would want to use weak
only outside (EX2), and other times when you would want to use the weak
outer, strong
inner, weak
inner dance (EX3), for example.
To examine the retain cycle problem, let's say a function is storing a reference to the block (i.e. heap) instead of referencing the function directly (i.e. stack). Many times we don't know the internals of a class/function, so it's safer to assume that the function is retaining the block.
Now you can easily create a retain cycle using weak
outer, and only using strong
inner (EX3_B):
public class CaptureListExperiment {
public init() {
}
var _someFunctionWithTrailingClosure: (() -> ())?
var _anotherFunctionWithTrailingClosure: (() -> ())?
func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting someFunctionWithTrailingClosure")
_someFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._someFunctionWithTrailingClosure!()
print("finishing someFunctionWithTrailingClosure")
}
}
func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
print("starting anotherFunctionWithTrailingClosure")
_anotherFunctionWithTrailingClosure = closure
DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
self?._anotherFunctionWithTrailingClosure!()
print("finishing anotherFunctionWithTrailingClosure")
}
}
func doSomething() {
print("doSomething")
}
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { // [weak self] in
self.doSomething()
}
}
}
// go ahead and add `deinit`, so I can see when this is deallocated
deinit {
print("deinit")
}
}
func performExperiment() {
let obj = CaptureListExperiment()
obj.testCompletionHandlers()
Thread.sleep(forTimeInterval: 1.3)
}
performExperiment()
/* Output:
starting someFunctionWithTrailingClosure
starting anotherFunctionWithTrailingClosure
finishing someFunctionWithTrailingClosure
doSomething
finishing anotherFunctionWithTrailingClosure
*/
Notice that deinit
is not called, since a retain cycle was created.
This could be fixed by either removing the strong
reference (EX2):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
//guard let self = self else { return }
self?.anotherFunctionWithTrailingClosure { // [weak self] in
self?.doSomething()
}
}
}
Or using the weak/strong/weak dance (EX3):
public func testCompletionHandlers() {
someFunctionWithTrailingClosure { [weak self] in
guard let self = self else { return }
self.anotherFunctionWithTrailingClosure { [weak self] in
self?.doSomething()
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With