How Unowned reference works with capture variables in Swift

360 Views Asked by At

There are a lot of tutorials on ARC. But I am not understanding the clear working of unowned or weak as how the reference captured variables becomes null.

Apple Document :

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.

class RetainCycle {
        var closure: (() -> Void)!
        var string = "Hello"

        init() {
            closure = { [unowned self] in
                self.string = "Hello, World!"
            }
        }
    }

the closure refers to self within its body (as a way to reference self.string), the closure captures self, which means that it holds a strong reference back to the RetainCycle instance. A strong reference cycle is created between the two. By unowned its breaking reference cycle.

But I want to understand which scenario both will not be mutually deallocated at the same time and Unowned self becomes null just want to crash it.?

2

There are 2 best solutions below

0
On

As I get, You ask How self can be null while closue is running. If I get this right I can give you a quite similar example this that I have seen before.

I wrote an extension to UIImageView that download image from given link and set itself like this.

public extension UIImageView{
  func downloadImage(link: String){
    let url = URL(string:link)
    URLSession.shared.dataTask(with: url){ [unowned self]
      if let image = UIImage(data: data){
        DispatchQueue.main.async{
          self.image = image
        }
      }
    }
    task.start()
  }
}

But there was a problem. Downloading an image is a background task. I set completion method to UrlSession and increased its reference count. So, my closure remains even if imageView is deaollecated.

So What happens if I close my viewController that holds my UIImageView, before download completed. It crashes because of imageView is deallocated but closure still remains and tries to reach its image property. As I get, you want to learn this.

I changed unowned reference to weak to solve this problem.

0
On

which means that it holds a strong reference back to the RetainCycle instance

This isn't true. It has an unowned reference back to the RetainCycle instance. That's not the same thing as a strong reference.

But I want to understand which scenario both will not be mutually deallocated at the same time and Unowned self becomes nilI just want to crash it.?

Any time closure is captured by something outside of RetainCycle, and so outlives its owner:

var rc: RetainCycle? = RetainCycle()   // create an RC

let cl = rc?.closure  // Hold onto its closure

rc = nil // Deallocate the RC

cl?() // Access the closure safely, but its reference to `self` is invalid. Crash.

As a rule, closures that involve unowned self should be impossible to reference outside of self. It's sometimes difficult to know this is true. For example, here is a case that recently crashed an app I work on:

var completion: (() -> Void)?

...

DispatchQueue.main.async { [unowned self] in
    self.completion()
    self.completion = nil
}

This feels fine, but if self is deallocated between the time it enqueues the main-queue block and the time that block runs, boom.

BTW, in this case the right answer would be a regular, strong self. We want the retain loop to keep this object around until its completion handler runs, at which point the block goes away, the reference to self goes away, and self is properly deallocated. So [weak self] is not always the answer, either.