简体   繁体   中英

Why does [weak self] work but [unowned self] break in a Swift closure?

This SpriteKit action repeats by calling itself with a completion closure. It uses a closure, rather than an SKAction.repeatActionForever() , because it needs to generate a random variable each repetition:

class Twinkler: SKSpriteNode {
  init() {
    super.init(texture:nil, color:UIColor.whiteColor(), size:CGSize(width:10.0, height:10.0))
    twinkle()
  }
  func twinkle() {
      let rand0to1 = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
      let action = SKAction.fadeAlphaTo(rand0to1, duration:0.1)
      let closure = {self.twinkle()}
      runAction(action, completion:closure)
  }
}

I think I should be using [unowned self] to avoid a strong reference cycle with the closure. When I do that:

let closure = {[unowned self] in self.twinkle()}

It crashes with the error: _swift_abortRetainUnowned . But if I use [weak self] instead:

let closure = {[weak self] in self!.twinkle()}

It executes without error. Why would [weak self] work but [unowned self] break? Should I even be using either of these here?

The Twinkler object is strongly referenced elsewhere in the program, as a child of another node. So I don't understand how the [unowned self] reference is breaking. It shouldn't be deallocated.

I tried replicating this problem outside SpriteKit using dispatch_after() , but I was unable to.

If self could be nil in the closure use [weak self] .

If self will never be nil in the closure use [unowned self] .

If it's crashing when you use [unowned self] then self is probably nil at some point in that closure so you would need to use [weak self] instead.

The examples from the documentation are pretty good for clarifying using strong , weak , and unowned in closures:

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

This sounds like a bug. {[unowned self] in self.twinkle()} should work identically to {[weak self] in self!.twinkle()}

I had a similar crash recently. In my case, sometimes the object get newly initialized happened to have the exact same memory address as the deallocated one. The code however, will execute just fine if the two objects have different memory address.

So this is my crazy explanation. When swift put the strong reference to the closure and check its capture list, it checks whether the object has been deallocated or not if the variable in capture list says "unowned". It doesn't do the check if object is marked as "weak".

Since the object is guaranteed never been nil in the closure, it will never actually crash there.

So, probably a language bug. And my take on that is use weak rather than unowned.

To not get an error, it should be:

let closure = {[weak self] in self?.twinkle()}

not

let closure = {[weak self] in self!.twinkle()}

The exclamation after force unwraps which throws an error on nil. Unowned will throw an error if self is nil just like force unwrapping. When doing either of those two options you should use and guard or if statement to protect from nil.

This is just my reading of the documentation, but here's a theory.

Like weak references, an unowned reference does not keep a strong hold on the instance it refers to. Unlike a weak reference, however, an unowned reference is assumed to always have a value. Because of this, an unowned reference is always defined as a non-optional type. [ source ]

You said that the Twinkler object is strongly referenced as the child of another node, but children of SKNode are implicitly unwrapped optionals. My bet is that the issue isn't that self is being deallocated, but that when you try to create the closure Swift is balking at creating an unowned reference to an optional variable. As such, [weak self] is the right closure capture list to use here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM