Swift - Capture List self clarification

673 views Asked by At

After reading some articles and developer guide of apple, i'm still confused about Capture List in closure. What does it mean "capture", how does it work behind the scene in terms of unowned self and weak self? how the closure use self without owning the object? I thought it like making a copy of that object so when it get finished it's passed from the stack like value type, but i guess that i'm wrong. I'm hoping that someone here can make it more easier and clear for understanding, or linked me to a good article that answering this specific question. Thanks for advance

3

There are 3 answers

0
AudioBubble On

If we keep in mind that captured values are strong references in closures by default, we can assume that this can create retain cycles (bad stuff).

A capture list is an array of variables you can pass into a closure. The purpose of capture lists is to change the strenght of the variables that are passed in. This is used to break retain cycles.

For instance:

// strong reference
[label = self.myLabel!] in

// weak reference
[weak label = self.myLabel!] in

// unowned reference
[unowned self] in
1
Joakim Danielson On

My understanding, and it might be a bit simplified, is that it is about ownership and holding on to an object, meaning that as long as we claim ownership of an object it can not be freed from memory even another part of the code sets it to nil or similar.

With weakwe say that it is okay to destroy the object and that we will only use it if it is still around.

So when declaring self as weak in a closure we say that if self is still around when it's time to execute the closure we do so normally otherwise the closure will silently be ignored without generating an error.

0
Guy Kogus On

It's mainly to do with reference counting. Any instance that is used inside a closure (but was declared outside) is strongly referenced (i.e. its reference count is incremented). This can lead to retain cycles, e.g.

class MyClass {
    var myClosure: (() -> Void)!

    init() {
        myClosure = {
            self.foo()
        }
    }

    func foo() {
    }
}

In the above example the instance of MyClass retains a reference to myClosure and vice versa, meaning that the MyClass instance will stay in memory forever.

You can also have more complex/harder-to-spot retain cycles, so you need to really pay attention and if you ever have any doubts add some print calls to your class' deinit methods just to make sure (or use Instruments).

In order to avoid these issues you can mark objects being captured in closures as unowned or weak. This means that their reference count won't be increased and you can avoid these retain cycles. The above example could have either been done this way:

myClosure = { [weak self] in
    self?.foo()
}

or, better yet for this example, this way:

myClosure = { [unowned self] in
    self.foo()
}

While the first way is always safe and what you will more likely do, the unowned version is easy to reason in this example because you know that myClosure won't outlive self. However, if you're not 100% sure that self will always outlive the closure use weak.

Also note that you can mark how to capture multiple objects used inside the closure, just separate it by commas, e.g.

myClosure = { [weak self, unowned bar] in
    self?.foo(bar)
}