When developing with Swift, we can break the loop of strong references to class instances and closures by using weak or unowned. Today let’s talk about the similarities and differences between weak and unowned.

weak

In everyday development, we often use weak to mark proxies or use it in closures to avoid reference loops.

weak var delegate: SomeDelegate?

lazy var someClosure: () -> Void= {[weak self] in
    guard let self = self else { retrun }
    self.balabala
}
Copy the code

When we assign a value to a variable marked weak, its reference count does not change. And the weak reference variable is automatically set to nil when the object referenced by it is released. This is why weak references must be declared Optional.

unowned

Like weak, unowned can also reference a class instance without increasing the reference count.

unowned let someInstance: SomeClass

lazy var someClosure: () -> Void= {[unowned self] in
    self.balabala
}
Copy the code

When using unowned, we do not need to declare the variable Optional.

One thing to note. For a variable marked by unowned, even if its original reference has been freed, it retains an “invalid” reference to the freed object, which is not Optional, and will not be pointed to nil. So, when we try to access such a unowned reference, the program gets an error.

Let’s look at the following example code:

class SomeSingleton {
    
    static let share = SomeSingleton(a)func closure(closure: ((a) -> Void)? {DispatchQueue.global().asyncAfter(deadline: .now() + 2) { closure? ()}}}class Person {
    
    let someSingleton = SomeSingleton.share
    let portrait = UIImage(a)func testClosure(a) {
        someSingleton.closure { [unowned self] in
            print(self.portrait)
        }
    }
    
    deinit {
        print("Person is deinited")}}class ViewController: UIViewController {
    
    var person: Person?
    
    override func viewDidLoad(a) {
        super.viewDidLoad()
        
        person = Person() person? .testClosure() person =nil}}Copy the code

Here we define a singleton that provides a closure that fires two seconds later. We then refer to this singleton in the Person class. Finally, we instantiate a Person object in the ViewController and set it to nil after calling the testClosure() method.

After the program runs, we look at the console log. After person is denint, the console prints Person is deinited. After two seconds, the closure of the singleton fires, and the program attempts to access the Portrait property of Person. Since Person is nil at this point, we are trying to read an object that has been freed but unowned reference still exists. So the program throws an exception.

Person is deinited
Fatal error: Attempted to read an unowned reference but object 0x6000027b5bf0 was already deallocated2019-04-20
Copy the code

If we replace [unowned self] with [weak self], run the program again.

someSingleton.closure { [weak self] in
    print(self? .portrait) }Copy the code

Observe the console log. The singleton closure fires two seconds after Person is set to nil. Since we’re using weak in the closure, the program won’t break, self? .portrait has a value of nil.

Person is deinited
nil
Copy the code

weak vs. unowned

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.

Conversely, define a capture as a weak reference when the captured reference may become nil at some point in the future. Weak references are always of an optional type, and automatically become nil when the instance they reference is deallocated. This enables you to check for their existence within the closure’s body.

According to apple’s official documentation. When we know that the life cycles of two objects are not related, we must use weak. In contrast, non-strongly referenced objects that have the same or longer lifetime as strongly referenced objects should use unowned.

For example, a ViewControler can reference its SubView using unowned. Because the ViewControler’s lifetime must be longer than its SubView’s.

When using the service, you need to use weak as appropriate. The initialization method of the Service may be encapsulated by factory pattern or Service Locator. These services may at some point be refactored as singletons, at which point their life cycle changes.

Capture the list

In addition to the usual weak self and unowned self, we can also use the capture list to break the closure reference loop. Label the variables that need to be captured with weak self or unowned self.

someSingleton.closure { [weak portrait] in
    print(portrait)
}

/ * or * /

someSingleton.closure { [unowned portrait] in
    print(portrait)
}
Copy the code

The capture list can also be used to initialize new variables

/* Since UIImageView(image: portrait) returns the Optional value, unowned cannot be used to mark Optional variables, so we need to force unpack here. * /

someSingleton.closure { [unowned imageView = UIImageView(image: portrait)! ]in
    print(imageView)
}

/ * or * /

someSingleton.closure { [weak imageView = UIImageView(image: portrait)] in
    print(imageView)
}
Copy the code

The compiler gives a warning. Because these variables are only scoped inside the closure.

Instance will be immediately deallocated because variable 'imageView' is 'unowned'

/* 或者 */

Instance will be immediately deallocated because variable 'imageView' is 'weak'
Copy the code

Reference: Automatic Reference Counting