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

6

13

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.

Acroter answered 25/6, 2014 at 17:27 Comment(1)
On iOS 9.1, I was able to reproduce the crash even if the closure itself is not executed. Not sure if this is a bug, because it doesn't happen on iOS 9.3 https://mcmap.net/q/429090/-using-unowned-inside-of-a-capture-list-causing-a-crash-even-the-block-itself-isn-39-t-executedMakedamakefast
D
9

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

Drainpipe answered 20/9, 2014 at 21:3 Comment(4)
Yes. The problem evaporated with later versions of Xcode and Swift.Acroter
And I'm now experiencing it again in swift 1.2...... note that the crash is occurring when the closure is declared, not when it is invoked.Antipater
had this issue with Swift 2.2, using weak instead of unowned fixed it. Interestingly, when I inspected self inside closure, it was not nilCeltuce
@deville: "Interestingly, when I inspected self inside closure, it was not nil" I think that might be because of the way weak references are implemented in pure Swift -- weak references are not set to nil until you try to access them (and deinitialized objects are not freed until all weak references have been unset, to allow this). mikeash.com/pyblog/… Still doesn't explain why there would be a difference between weak and unowned though.Drainpipe
O
23

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

Ovary answered 27/10, 2014 at 23:41 Comment(0)
D
9

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

Drainpipe answered 20/9, 2014 at 21:3 Comment(4)
Yes. The problem evaporated with later versions of Xcode and Swift.Acroter
And I'm now experiencing it again in swift 1.2...... note that the crash is occurring when the closure is declared, not when it is invoked.Antipater
had this issue with Swift 2.2, using weak instead of unowned fixed it. Interestingly, when I inspected self inside closure, it was not nilCeltuce
@deville: "Interestingly, when I inspected self inside closure, it was not nil" I think that might be because of the way weak references are implemented in pure Swift -- weak references are not set to nil until you try to access them (and deinitialized objects are not freed until all weak references have been unset, to allow this). mikeash.com/pyblog/… Still doesn't explain why there would be a difference between weak and unowned though.Drainpipe
O
6

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.

Ocher answered 18/11, 2015 at 4:49 Comment(1)
I have exactly the same problem with unowned when the new initialized object has the same memory address as the deallocated one. Did you already filed a bug report?Manx
A
3

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.

Anisaanise answered 15/6, 2016 at 19:49 Comment(5)
The OP stated that second example (forcefully unwrapped weak self) doesn't throw the error. This question is not about best practices, but rather why using unowned self inside of a capture list throws an error in his example (and why forced unwrapping of weak self doesn't throw an error). ;)Makedamakefast
I just tried it in a playground and forcefully unwrapping weak self if self is nil does throw an error. So having it like I showed will never throw an error while force unwrapping it will if the object (self) is set to nil will before the closure is run. @MakedamakefastAnisaanise
I belive you. That is how everything should work. But OP says it works differently. for him... Also if you have time, take a look into my own question about similar issue (you can find a link in a comment of original poster's question).Makedamakefast
Yeah I guess I was just adding my two cents on best practices lol @MakedamakefastAnisaanise
It is not safe to force-unwrap a weak self, even in playground you were lucky. But that just means that in that case that could be unwrapped. If you have an async task and during that your referenced object that weak self refers to get deallocated, it will crash.Consultative
D
1

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.

Dyanna answered 25/6, 2014 at 19:1 Comment(1)
Keeping a strong reference to the Twinkler object as a non-optional constant in the scene object still results in the same abortRetainUnowned runtime crash.Acroter
W
0

See the example below, and you will see that force-unwrapping can cause crash, no matter what is suggested above in https://mcmap.net/q/844566/-why-does-weak-self-work-but-unowned-self-break-in-a-swift-closure

import Foundation

class WeaklyReferenced {

    var delayedCallback: DelayedCallback?

    func runDelayedCallbackRight() {
        delayedCallback = DelayedCallback()
        delayedCallback?.delayedCallback { [weak self] in
            self?.myDuty()
        }
    }

    func runDelayedCallbackWrong() {
        delayedCallback = DelayedCallback()
        delayedCallback?.delayedCallback { [weak self] in
            self!.myDuty()
        }
    }

    func myDuty() {
        print("This is my duty to print this message")
    }
}

class DelayedCallback {
    func delayedCallback(callback: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            callback()
        }
    }
}

class Runner {
    var weakly: WeaklyReferenced?


    func runWithoutPrintingRight() {
        weakly = WeaklyReferenced()
        weakly?.runDelayedCallbackRight()
        weakly = nil
    }

    func runSuccessfullyRight() {
        weakly = WeaklyReferenced()
        weakly?.runDelayedCallbackRight()
    }

    func runWrong() {
        weakly = WeaklyReferenced()
        weakly?.runDelayedCallbackWrong()
        weakly = nil
    }
}

let runner = Runner()

/**
 The order is relevant: as you can see `runWithoutPrintingRight` will break the reference
 Running `runWithoutPrintingRight` immediatelly after calling `runSuccessfullyRight` will cause `runSuccessfullyRight` not to print because of the broken reference
`runWrong` will cause a crash
 */

// runner.runWithoutPrintingRight() // This method will break the reference

runner.runSuccessfullyRight()

// runner.runWrong()
Weide answered 2/8 at 12:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.