7

I have this small Swift script, which uses weak references:

#!/usr/bin/env swift

class Thing
{
    deinit
    {
        print("Thing object deallocated")
    }
}

class WeakThing
{
    weak var thing: Thing?
    {
        didSet
        {
            print("Set thing to \(thing)")
        }
    }
}

var thing = Thing()

let weakThing = WeakThing()
weakThing.thing = thing

thing = Thing()
print("weakThing's thing is \(weakThing.thing)")

This prints:

Set thing to Optional(Test.Thing)
Thing object deallocated
weakThing's thing is nil

However, I would expect it to print:

Set thing to Optional(Test.Thing)
Set thing to nil
Thing object deallocated
weakThing's thing is nil

What am I doing incorrectly? I see that the object is being deallocated, and that the value of the thing variable is changing, but my didSet code is not executing.

nhgrif
  • 61,578
  • 25
  • 134
  • 173

2 Answers2

8

didSet and willSet are not called when a weak-reference is auto-zeroed due to ARC.

If you were to manually set the property to nil, you would see the didSet code called.

nhgrif
  • 61,578
  • 25
  • 134
  • 173
  • @nsm If you're designing things correctly, an object shouldn't care if one of its weak references has deallocated. – nhgrif Oct 22 '15 at 12:23
  • 4
    that is not true in all cases - I was attempting to build a framework that centered on weak references, but it's impossible. –  Oct 22 '15 at 13:10
3

I know this question is very old, but I stumbled across another answer that actually get's the problem solved here: https://stackoverflow.com/a/19344475/4069976

For what it's worth, this is my implementation to watch a deinit as suggested by the answer referenced above. Just make sure you don't create any retain cycles with your onDeinit closure!

private var key: UInt8 = 0

class WeakWatcher {
    private var onDeinit: () -> ()

    init(onDeinit: @escaping () -> ()) {
        self.onDeinit = onDeinit
    }

    static func watch(_ obj: Any, onDeinit: @escaping () -> ()) {
        watch(obj, key: &key, onDeinit: onDeinit)
    }

    static func watch(_ obj: Any, key: UnsafeRawPointer, onDeinit: @escaping () -> ()) {
        objc_setAssociatedObject(obj, key, WeakWatcher(onDeinit: onDeinit), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
    }

    deinit {
        self.onDeinit()
    }
}

Call it like this when initializing your weak var:

self.weakVar = obj
WeakWatcher.watch(obj, onDeinit: { /* do something */ })
fabianm
  • 101
  • 1
  • 4