28

Let's say I have a weak var view: UIView? in my class Button {}. Is there any way to know when view loses its reference and becomes nil?

I tried using weak var view: UIView? {} (aka a computed property) in order to override set {}, but that didn't work because now it's a computed property and can't store a weak reference (how annoying!).

Edit:

@fqdn's answer didn't work with this code... Try it in an Xcode Playground

import UIKit

class Test {
  weak var target: UIView? {
    willSet {
      if !newValue { println("target set to nil") }
      else { println("target set to view") }
    }
  }
}

class Button {
  var view: UIView? = UIView()
}

var t = Test()
var b = Button()
t.target = b.view
b.view = nil // t.target's willSet should be fired here

Your output console should display:

target set to view
target set to nil

My console displays

target set to view

b.view is the strong reference for the UIView instance. t.target is the weak reference. Therefore, if b.view is set to nil, the UIView instance is deallocated and t.target will be equal to nil.

aleclarson
  • 18,087
  • 14
  • 64
  • 91
  • 3
    [from REPL](http://pastebin.com/PVVNcy62), as I expected, you can't observer when weak var set to nil – Bryan Chen Jun 19 '14 at 23:33
  • 1
    @0x7fffffff thats the whole point of the question... we shouldn't need to do it – Bryan Chen Jun 19 '14 at 23:34
  • the ObjC way to do it is use associated object, not sure what is the Swift way – Bryan Chen Jun 19 '14 at 23:36
  • @BryanChen gets it. `b.view` is the strong reference for the UIView instance. `t.target` is the weak reference. Therefore, if `b.view` is set to `nil`, the UIView instance is deallocated and `t.target` will be equal to nil. – aleclarson Jun 19 '14 at 23:36
  • @BryanChen Ohh, I completely misunderstood the question. – Mick MacCallum Jun 19 '14 at 23:36
  • 1
    the point being, in the end t.target will be 'equal' to nil, but it wasn't assigned ('set to') nil, so its property observers won't be fired – fqdn Jun 19 '14 at 23:50

1 Answers1

17

If your button is holding a reference to another view, it should either be an owner of that view (i.e., it should hold a strong reference) or it should not care when that view goes away (i.e., its weak reference to it becomes nil.) There is no notification when weak references become nil, and that is by design.

In particular, Swift property observers are not called when weak references become nil, as the following code demonstrates:

class A : CustomStringConvertible {
    var s: String?

    init(s: String) {
        self.s = s;
        print("\(self) init")
    }

    deinit {
        print("\(self) deinit")
    }

    var description: String {
        get { return "[A s:\(s ?? "nil")]" }
    }
}

class B : CustomStringConvertible {
    weak var a:A? {
        willSet {
            print("\(self) willSet a")
        }
        didSet {
            print("\(self) didSet a")
        }
    }

    init(a: A?) {
        self.a = a
        print("\(self) init")
    }

    deinit {
        print("\(self) deinit")
    }

    var description: String {
        get { return "[B a:\(a == nil ? "nil" : String(describing: a!))]" }
    }
}

func work() {
    var a: A? = A(s: "Hello")
    var b = B(a: a)
    print("\(b)")
    a = nil
    print("\(b)")
    b.a = A(s: "Goodbye")
}

work()

When work() is called, the console gives the following output:

[A s:Hello] init
[B a:[A s:Hello]] init
[B a:[A s:Hello]]
[A s:Hello] deinit
[B a:nil]
[A s:Goodbye] init
[B a:nil] willSet a
[B a:[A s:Goodbye]] didSet a
[A s:Goodbye] deinit
[B a:nil] deinit

Notice that in neither case of the instance of A deallocating and its weak reference in the instance of B becoming nil are the property observers called. Only in the direct case of assignment to B.a are they called.

Wolf McNally
  • 3,647
  • 1
  • 16
  • 13
  • 1
    Hmm. What's the use in not being able to know when a weak var becomes nil? – aleclarson Jun 19 '14 at 23:30
  • You're supposed to architect your code so you really don't care if or when it becomes nil. So for instance you can fire off a block via Grand Central Dispatch that may execute at some later time, and may hold weak references to one or more objects. When it finally does execute if those references are nil it can decide what to about that at that time, including do nothing, in which case its function has been effectively canceled. – Wolf McNally Jun 19 '14 at 23:46
  • 8
    Let's say I'm forced to use a `weak var` in order to avoid a retain cycle. Why am I forced into this *not caring* attitude when I do actually want to care? I must be missing something obvious in software architecture... – aleclarson Jun 19 '14 at 23:55
  • Why do you **want** to care? if A holds a reference to B and B holds a reference to A, you've got to break the retain cycle somehow. In your example above, you have a view and a button, and they apparently hold references to each other. But you don't say **why** you need one to be notified when the other goes away. If the view is the button's superview, you can override didMoveToSuperview() to be notified when the button is removed from its superview. (continues...) – Wolf McNally Jun 20 '14 at 00:09
  • (...continued) If the view is a subview of the button, then the button should own the subview and can keep a strong reference. If it's somewhere else in the view hierarchy, then you should be using a weak reference, because it might go away out of your button's control at any time. But the button shouldn't need to proactively respond to this event, and if it did, you can use NSNotificationCenter to post a general notification from the view going away to anyone interested in hearing about it. – Wolf McNally Jun 20 '14 at 00:09
  • Thanks for the explanations. My example in the question is entirely contrived though. – aleclarson Oct 23 '14 at 15:13
  • 1
    It really doesn't matter that they're contrived— they served to help me illustrate several of the typical options. – Wolf McNally Oct 23 '14 at 20:20
  • This seems kind of like a "best of possible worlds" justification to me. Adding "willDenit" would allow people to work around other people's mistakes (like Apple's NSNotification implementation). Since we can't control everything about the environment in which we write code, it would be nice to have the flexibility. – original_username Nov 14 '16 at 15:02
  • @Charlesism The problem with making it easy to "work around other people's mistakes" is then that's what happens. And workarounds work around workarounds, round and round until you have a huge ball of technical debt and tears. – Wolf McNally Nov 17 '16 at 19:30
  • @RobertMcNally ...I'll take a "huge ball of technical debt and tears" over NSNotification. – original_username Nov 17 '16 at 23:15
  • 1
    @Charlesism There are plenty of alternatives to NSNotification. These days I prefer delegate closures. I almost never use NSNotification anymore except when receiving notifications from the OS, and I often write wrapper classes for those that allow me to use closures instead. – Wolf McNally Dec 13 '16 at 00:02
  • 1
    hi @RobertMcNally, thanks for pointing out clearly that, in fact, it seems like in Swift you can **not** be notified when a weak var becomes nil. But for me it's a plain shortcoming of the language: I just can't agree that it is anti-pattern to know that. *In the examples you guys have discussed* it would be anti-pattern to know that. But you can instantly give examples where you need to know that. You guys mention notifications - say you're holding a collection of weak vars pointing to somethings. To do clean up, you have to stupidly scan through them from time to time and remove duds... – Fattie Jan 30 '17 at 12:36
  • 1
    ...it would be far better if one could remove the box when the runtime tells you they have gone to nil. – Fattie Jan 30 '17 at 12:37
  • @JoeBlow It's interesting that you say you can "instantly give examples of where you need to know that," but then fail to do so. If I could attach a closure to any weak pointer saying, "call this if you ever go to nil" then that would add a huge amount of overhead to the runtime without much benefit. If a box goes away it can simply send a notification, "Hey, I've gone away," and anyone (or no-one) interested can respond. If you're holding an array of weak pointers, by definition they are *optionals* so you always need to deal with the case where any of them might be nil anyway. – Wolf McNally Jan 31 '17 at 18:47
  • Hey Rob, you misunderstood my sentence structure: writing notifications is a great ("instant!") **example of** where you need to know when a weak reference has gone away. Anything at all where you are keeping a list of items, and you need to know when one goes away - rather than periodically cleaning up - is a great example of when you could use this. (the alternative is, the consumer annoyingly has to register them, to achieve any of the workarounds you mention.) By all means, all your points are valid. – Fattie Jan 31 '17 at 19:07
  • 1
    @JoeBlow Fair enough. It also occurs to me that if you could be notified when a weak pointer goes to nil, what would you have? A notification that an object no longer exists and a nil pointer to that object! Since it *is* nil, you wouldn't even know the exact type of the object. Since might be stored in any kind of structure (or just a local variable), you would have to magically know it's index or key or some other way of locating the now-nil pointer to clean up the "dud". So if you know of a language that *does* do this, I'd like to know which so I can learn how Swift should be doing it. – Wolf McNally Feb 01 '17 at 17:50
  • 1
    @JoeBlow Also, if you could be notified *before* a weak pointer goes to nil, you'd have to be prevented from taking any retained references it at all during that notification. To do so would either have to abort the deinitialization of the object, or leave one with dangling references. And since it's hard to do anything with an object without taking retained references to it, any of which could be long-lived, it seems quite problematic from that angle too. – Wolf McNally Feb 01 '17 at 17:54
  • 1
    @JoeBlow This is why the "invalidate" pattern exists. By invalidating an object, you give observers of that object a chance to clean up and release references to it before the primary owner of the object releases its last reference, which should be the only one left, triggering deinitialization. – Wolf McNally Feb 01 '17 at 17:56
  • **It also occurs to me that if you could be notified when a weak pointer goes to nil, what would you have** HELL OF A GOOD POINT MAN @RobertMcNally – Fattie Feb 01 '17 at 18:03
  • 1
    I can't stand answers that just say "don't do that." Answer the damn question or don't submit at all. – Eddie Sullivan Jul 17 '17 at 20:11
  • @EddieSullivan Um— "accepted answer"? That *is* the answer. You can't do it and it you read the comments, you'll see why it wouldn't make any sense if you could. – Wolf McNally Jul 18 '17 at 02:15
  • here's a use case. I'm using a wrapper class(say class A) which contains a weak reference to another(say class B). I have a collection of class A, and I'd like to sanitise the collection when class B is deallocated(I don't own class B's lifecycle). – Aswath Mar 20 '23 at 12:09