Skip to content

Instantly share code, notes, and snippets.

@ashfurrow
Last active August 29, 2015 14:03
Show Gist options
  • Save ashfurrow/b9fa03387ca8c68bc07e to your computer and use it in GitHub Desktop.
Save ashfurrow/b9fa03387ca8c68bc07e to your computer and use it in GitHub Desktop.
Crashing on Unowned reference access

Related to ashfurrow/ModalNotificationController#4

Consider the following class structure:

@objc protocol MyProtocol: NSObjectProtocol {
    func method() -> ()
}


class MyObject : NSObject {
    unowned let delegate: MyProtocol
    
    init(delegate: MyProtocol) {
        self.delegate = delegate
        super.init()
    }
    
    func doSomethingWithDelegate() {
        delegate.method()
    }
}

class DelegateClass: NSObject, MyProtocol {
    func method () {
        for i in 1...10 {
            "Hello!"
        }
    }
}

Now let's use them in an app (it just doesn't work in a Playground)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
                            
    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        
        
        let delegate = DelegateClass()
        let object = MyObject(delegate: delegate)
        
        object.doSomethingWithDelegate() // Crashes here
        println("\(delegate)")
        
        
        return true
    }

}

When I try and access the delegate, boom, crash. Here's the trace:

* thread #1: tid = 0x8f817d, 0x001ba570 libswift_stdlib_core.dylib`_swift_release_slow, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x001ba570 libswift_stdlib_core.dylib`_swift_release_slow
    frame #1: 0xbfffcf98
    frame #2: 0x001c8f7e libswift_stdlib_core.dylib`swift_unknownRetainUnowned + 318
    frame #3: 0x00003470 Bug`@objc Bug.MyObject.delegate.getter : @unowned Bug.MyProtocol + 64 at AppDelegate.swift:0
  * frame #4: 0x0000364c Bug`Bug.MyObject.doSomethingWithDelegate (self=0x0b20ff70)() -> () + 60 at AppDelegate.swift:29
    frame #5: 0x000036c2 Bug`@objc Bug.MyObject.doSomethingWithDelegate (Bug.MyObject)() -> () + 34 at AppDelegate.swift:0
    frame #6: 0x00003e95 Bug`Bug.AppDelegate.application (application=0x018fe804, launchOptions=None, self=0x0b500040)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional<ObjectiveC.NSDictionary>) -> Swift.Bool + 149 at AppDelegate.swift:56
    frame #7: 0x00003f55 Bug`@objc Bug.AppDelegate.application (Bug.AppDelegate)(ObjectiveC.UIApplication, didFinishLaunchingWithOptions : Swift.Optional<ObjectiveC.NSDictionary>) -> Swift.Bool + 101 at AppDelegate.swift:0
    frame #8: 0x00c44b37 UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 291
    frame #9: 0x00c45875 UIKit`-[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2920
    frame #10: 0x00c48a33 UIKit`-[UIApplication _runWithMainScene:transitionContext:completion:] + 1507
    frame #11: 0x00c60eb8 UIKit`__84-[UIApplication _handleApplicationActivationWithScene:transitionContext:completion:]_block_invoke + 59
    frame #12: 0x00c4777e UIKit`-[UIApplication workspaceDidEndTransaction:] + 29
    frame #13: 0x03404f1f FrontBoardServices`-[FBSWorkspace clientEndTransaction:] + 87
    frame #14: 0x0340c4ed FrontBoardServices`__53-[FBSWorkspaceClient _queue_handleTransactionBookEnd]_block_invoke + 49
    frame #15: 0x00371f90 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 16
    frame #16: 0x00367133 CoreFoundation`__CFRunLoopDoBlocks + 195
    frame #17: 0x00366898 CoreFoundation`__CFRunLoopRun + 936
    frame #18: 0x0036622b CoreFoundation`CFRunLoopRunSpecific + 443
    frame #19: 0x0036605b CoreFoundation`CFRunLoopRunInMode + 123
    frame #20: 0x00c47095 UIKit`-[UIApplication _run] + 571
    frame #21: 0x00c4a6e5 UIKit`UIApplicationMain + 1526
    frame #22: 0x00004691 Bug`top_level_code + 97 at AppDelegate.swift:43
    frame #23: 0x000046cb Bug`main + 43 at AppDelegate.swift:0
    frame #24: 0x022b9ac9 libdyld.dylib`start + 1

I've filed a radar.

@diederikh
Copy link

When removing NSObjectProtocol you get the following compiler error:

'unowned' cannot be applied to non-class type 'MyProtocol'; consider adding a class bound

Looks like unowned cannot by used by protocols.

@ashfurrow
Copy link
Author

Hmm. Looks more like unowned references don't behave the way I expect them to. I've replaced the definitions of delegate: MyProtocol with delegate: DelegateClass and it still crashes.

@ochococo
Copy link

ochococo commented Jul 6, 2014

This works and it's perfectly fine for delegate to be weak var!

@objc protocol MyProtocol: NSObjectProtocol {
    func method()
}


class MyObject : NSObject {
    weak var delegate: MyProtocol?

    init(delegate: MyProtocol) {
        self.delegate = delegate
        super.init()
    }

    func doSomethingWithDelegate() {
        delegate?.method()
    }
}

class DelegateClass: NSObject, MyProtocol {
    func method () {
        for i in 1...10 {
           println("Hello!")
        }
    }
}

https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/AutomaticReferenceCounting.html

@RuiAAPeres
Copy link

In theory the only strong reference to the unowned is created here:

        let delegate = DelegateClass()

It should last until the end of the method, unless there is something the compiler is doing.

@ochococo
Copy link

ochococo commented Jul 6, 2014

Oh and another bug is here (also fixed previously but not mentioned):

func method() -> ()

Where's that method returning closure/block implemented? I don't see any.

EDIT:
Sure... My bad... "-> ()" means "return void."... Double vision ;)
But why even bother putting ->() there? Hmm?

@ashfurrow
Copy link
Author

First, I don't want to use an optional for my delegate, as it is required for my object to function. Second, there is not func that returns a closure. "-> ()" means "return void."

@RuiAAPeres
Copy link

I see the logic where A wants to delegate some work to B. And B only makes sense to exist while A is alive. It's slightly different from the typical delegation of a UITableView where the delegate normally is a ViewController and makes sense to be weak. From my perspective what could make sense would be to simply:

`var delegate: MyProtocol`

Since MyObject is the only instance that has a strong reference to it, when it's released so it will be the delegate.

@ashfurrow
Copy link
Author

But if the delegate retains a strong reference to the object, you have a reference cycle.

@ochococo
Copy link

ochococo commented Jul 6, 2014

First, in given sample I see only one reference: let delegate: MyProtocol. For retain cycle you need at least two. So... Rather bad example here.

Second it's not delegation but rather some serious friendship there, maybe even love (with a shade of quirky composition?). :) Two objects, one uses another forever and ever :) If you need that object forever, strong reference is always a good way to ensure that. But I wouldn't call the second object my delegate. I know it's sometimes (I mean close to never) necessary to retain delegate but not when "something there" (not your code) can deallocate one of the two. From what I understood unowned is (should) be applied when both objects (should) deallocate at the same time. Are you sure it's always the case? If so - I would eat my hat if that's a proper delegation.

But nonetheless that behaviour and crash is pretty strange. I would've also filled a radar for Apple to ignore. :)

@samirGuerdah
Copy link

I think that there is two problems here :

The first is with design, the objet who delegate should work without it's delegate, that means that you should put your delegate as optional ( @ochococo's solution) or just make a composition. In short i am okay with the suggestion of @ochococo. :)

The second, the crash is anormal, fill a radar :).

@roop
Copy link

roop commented Jul 7, 2014

Per devforums, this is fixed in Beta 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment