When to use an unowned reference for closure

Apple's documentations says "define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time"


But if a closure and a class instance refer to each other and have a strong reference cycle, by definition, they will never be deallocated. How can they be deallocated at the same time when they can never be deallocated at all due to their strong reference cycle?

Accepted Reply

when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time

You may need to interpret this description more conceptually than actual deallocation.


First forget about the strong reference cycles which prevents actual deallocation and check this example:

class MyClass {
    var myClosure: (()->Void)?
    
    init() {
        myClosure = {
            self.aMethod()
        }
    }
    
    func aMethod() {
        print(#function)
        //...
    }
}

As you know, the closure (on line 05.-07.) captures `self`, so,

When the closure is deallocated, a strong reference to `self` is released.


And, an instance property `myClosure` holds a strong reference, assuming you make no other strong reference elsewhere,

When the instance is deallocated, a strong reference to the closure is released.


This is the case the doc is saying, the instance and the closure will always be mutually referring, and always (conceptually) be deallocated at the same time. You should better use `unowned` in such a case.

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

With using `unowned`, the instance and the closure will always (actually) be deallocated at the same time.

Replies

That is the problem with strong references that can create cycles.


If you want very detailed explanations, look at the note in Swift Programming language,

Capturing Values paragraph

« If you assign a closure to a property of a class instance, and the closure captures that instance by referring to the instance or its members, you will create a strong reference cycle between the closure and the instance. Swift uses capture lists to break these strong reference cycles. For more information, see Strong Reference Cycles for Closures. »

Extrait de: Apple Inc. « The Swift Programming Language (Swift 4). » Apple Books.


And the details for cycles

Strong Reference Cycles for Closures

You saw above how a strong reference cycle can be created when two class instance properties hold a strong reference to each other. You also saw how to use weak and unowned references to break these strong reference cycles.


A strong reference cycle can also occur if you assign a closure to a property of a class instance, and the body of that closure captures the instance. This capture might occur because the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.

This strong reference cycle occurs because closures, like classes, are reference types. When you assign a closure to a property, you are assigning a reference to that closure. In essence, it’s the same problem as above—two strong references are keeping each other alive. However, rather than two class instances, this time it’s a class instance and a closure that are keeping each other alive.


Swift provides an elegant solution to this problem, known as a closure capture list. However, before you learn how to break a strong reference cycle with a closure capture list, it’s useful to understand how such a cycle can be caused.


The example below shows how you can create a strong reference cycle when using a closure that references self. This example defines a class called HTMLElement, which provides a simple model for an individual element within an HTML document:



class HTMLElement {
 
    let name: String
    let text: String?
 
    lazy var asHTML: () -> String = {
        if let text = self.text {
            return "<\(self.name)>\(text) »
     } else {
            return "<\(self.name) />"
        }
    }
  
    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }
  
    deinit {
        print("\(name) is being deinitialized")
    }
  
}


The HTMLElement class defines a name property, which indicates the name of the element, such as "h1" for a heading element, "p" for a paragraph element, or "br" for a line break element. HTMLElement also defines an optional text property, which you can set to a string that represents the text to be rendered within that HTML element. »

In addition to these two simple properties, the HTMLElement class defines a lazy property called asHTML. This property references a closure that combines name and text into an HTML string fragment. The asHTML property is of type () -> String, or “a function that takes no parameters, and returns a String value”.


By default, the asHTML property is assigned a closure that returns a string representation of an HTML tag. This tag contains the optional text value if it exists, or no text content if text does not exist. For a paragraph element, the closure would return "<p>some text</p>" or "<p />", depending on whether the text property equals "some text" or nil.


The asHTML property is named and used somewhat like an instance method. However, because asHTML is a closure property rather than an instance method, you can replace the default value of the asHTML property with a custom closure, if you want to change the HTML rendering for a particular HTML element.


For example, the asHTML property could be set to a closure that defaults to some text if the text property is nil, in order to prevent the representation from returning an empty HTML tag.

………

« Resolving Strong Reference Cycles for Closures »


« You resolve a strong reference cycle between a closure and a class instance by defining a capture list as part of the closure’s definition. A capture list defines the rules to use when capturing one or more reference types within the closure’s body. As with strong reference cycles between two class instances, you declare each captured reference to be a weak or unowned reference rather than a strong reference. The appropriate choice of weak or unowned depends on the relationships between the different parts of your code.


NOTE

Swift requires you to write self.someProperty or self.someMethod() (rather than just someProperty or someMethod()) whenever you refer to a member of self within a closure. This helps you remember that it’s possible to capture self by accident. »

Thanks a lot for the answer but it's not really what I was asking. Let me try and ask my question in a different way.


Apple says "define a capture in a closure as an unowned reference when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time"


Now let me paraphrase that quote in my own words: "In order to use the unowned keyword in a capture list to resolve a strong reference cycle between a closure and a class instance, two conditions must be met. 1)the closure and the instance it captures will always refer to each other. 2)the closure and the instance it captures will always deallocate at the same time."


But this is actually self-contradictory because if the 2nd condition can be met, that is, if the closure and the instance it captures can deallocate at the same time, a strong reference cycle between the closure and the instance wouldn't even exist. By definition, if there's a strong reference cycle between a closure and an instance, neither the closure nor the instance can deallocate. So if the closure and the instance it captures can deallocate at the same time (or can deallocate at all), it means that there's no strong reference cycle between them. And therefore there's no need for a capture list to resolve a strong reference cycle. And that quote from Apple's documentation is basically saying, in order to resolve a strong reference cycle between a closure and an instance by using "unowned" in a capture list, one of the conditions that has to be met is that there's no strong reference cycle between the closure and the instance. That's like saying, there's a solution to a problem, and in order to use that solution to solve the problem, one of the things you have to make sure is that you don't have that problem that the solution is designed to solve. Which makes absolutely no sense.


My question is, how should I interpret and understand this seemingly self-contradictory quote by Apple? Am I misinterpreting the quote? or did Apple make a mistake when writing the documentation?


Sorry, I know my question sounds extremely confusing but I hope you can understand what I'm asking here. Thanks in advance.

I interpret this differently. And do not see contradiction, may be some imprecision.

In fact it saysyou deallocate (I understand: try to deallocate)


"In order to use the unowned keyword in a capture list to resolve a strong reference cycle between a closure and a class instance, two conditions must be met. 1)the closure and the instance it captures will always refer to each other. 2)the closure and the instance it captures will always deallocate at the same time."


If you want to resolve strong reference cycle with unowned, 2 conditions must be met (when you have declared unowned):

- the closure and the instance it captures will always refer to each other: agree, this "always refer" is not clear. Maybe it means that if unowned, you need to refer to instance in the closure ?

- the closure and the instance it captures will always deallocate at the same time ; I understand that you should not deallocate one and not the other if you have declared unowned.


You could file a bug report against doc to ask for some clarification. ANd an example of what is "illicit" and what is correct.

when the closure and the instance it captures will always refer to each other, and will always be deallocated at the same time

You may need to interpret this description more conceptually than actual deallocation.


First forget about the strong reference cycles which prevents actual deallocation and check this example:

class MyClass {
    var myClosure: (()->Void)?
    
    init() {
        myClosure = {
            self.aMethod()
        }
    }
    
    func aMethod() {
        print(#function)
        //...
    }
}

As you know, the closure (on line 05.-07.) captures `self`, so,

When the closure is deallocated, a strong reference to `self` is released.


And, an instance property `myClosure` holds a strong reference, assuming you make no other strong reference elsewhere,

When the instance is deallocated, a strong reference to the closure is released.


This is the case the doc is saying, the instance and the closure will always be mutually referring, and always (conceptually) be deallocated at the same time. You should better use `unowned` in such a case.

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

With using `unowned`, the instance and the closure will always (actually) be deallocated at the same time.

Ah I understand now. The doc isn't talking about literal deallocation.


So in order to use the unowned keyword in a capture list to resolve a strong reference cycle between a closure and a class instance it captures, two conditions must be met

1) the closure and the instance it captures will always refer to each other.

2)In a hypothetical world where the closure and the instance can both be deallocated despite their strong reference cycle, the closure and the instance will always deallocate at the same time.