Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do capture lists of inner closures need to redeclare `self` as `weak` or `unowned`?

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!

like image 550
Nirma Avatar asked Aug 03 '16 09:08

Nirma


People also ask

Why do we use weak self in closure?

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.

What is closure capture list?

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.

How to handle weak capture in a closure?

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.

What is the use of capture list in closure?

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.

How to unwrap self in a closure?

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.

Is self-captured as weak and sampledelegate as unowned required?

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.


2 Answers

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.

like image 113
Rob Avatar answered Oct 08 '22 19:10

Rob


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:

  1. To prevent retain cycles.
  2. To prevent objects living longer than they should be.

More on #2 (preventing long lived objects)

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.

More on #1 (preventing retain cycles)

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()
        }
    }
}
like image 31
Senseful Avatar answered Oct 08 '22 17:10

Senseful