1

Perhaps this question is more general than I will make it seem, but I wanted to make sure I showed my full context in case something there is the cause of this issue.

  1. I wrote a singleton class with a KVC-compliant property and two methods:
class Singleton: NSObject {
    static let sharedInstance = Singleton()

    @objc dynamic var aProperty = false

    func updateDoesntWork() {
        aProperty = !aProperty
    }

    func updateDoesWork() {
        Singleton.sharedInstance.aProperty = !aProperty
    }
}
  1. I add an observer for the property in my app delegate's setup code:
Singleton.sharedInstance.addObserver(self, forKeyPath: #keyPath(Singleton.aProperty), options: [.new], context: nil)
  1. I override my app delegate's observeValue() method:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    NSLog("observeValue(forKeyPath: \(String(describing:keyPath)), of: \(String(describing:object)), change: \(String(describing:change)), context:\(String(describing:context)))")
}

Now, if I call Singleton.sharedInstance.updateDoesntWork(), I don't get a log entry for the change in aProperty. The property is changed (I verified this in the debugger), it's just that no notification is sent.

Whereas, if I call Singleton.sharedInstance.updateDoesWork(), everything works as I would expect -- the property is also changed, of course, but most importantly, this time the observer is notified of the change (the log entry is printed).

It makes no sense to me that I should need the full Singleton.sharedInstance.aProperty rather than just aProperty for KVO to work. What am I missing?

swineone
  • 2,296
  • 1
  • 18
  • 32
  • I can't reproduce this. Both methods log for me without trouble. I would try stripping this down to precisely what you've described here and trying it in a small test app. For example, this is the playground I used (I also tested in an iOS app using the app delegate): https://gist.github.com/rnapier/4500b87cd3a253020be56e8bd03f54d3 The most likely cause is that you aren't observing the object you think you are, or that you're calling `updateDoesntWork` before you observe. – Rob Napier Jan 29 '19 at 13:29
  • Same here. Cannot reproduce (in a command line app). – Martin R Jan 29 '19 at 13:31
  • Will do, and add an MCVE to the question. – swineone Jan 29 '19 at 13:43
  • 3
    Be aware that without making the initialiser of `Singleton` private, you don't actually guarantee that only the `shared` instance will be created, you only guarantee that you'll always have access to that specific instance. Moreover, why aren't you using Swift 4's KVO instead of that old Obj-C one? See [Key-Value Observing in Swift](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift) – Dávid Pásztor Jan 29 '19 at 13:51
  • @DávidPásztor you nailed the issue for me -- I had forgotten to add a private init(), and then I was creating an instance of my "Singleton" (which actually wasn't a singleton a couple of hours ago, I just converted it to one) in the app delegate. The method was being called on this instance, not the shared instance. When I changed the variable on the app delegate to the shared instance, the issue went away. Thanks! – swineone Jan 29 '19 at 13:56
  • 1
    @swineone: ... which means that what you posted above is not the real code showing the problematic behavior. – Martin R Jan 29 '19 at 13:58
  • 1
    @MartinR indeed, and I apologize for that. If I had actually tried to create a real MCVE, I would have spotted the part where I wrote `var singleton = Singleton()` in my app delegate rather than the correct `var singleton = Singleton.sharedInstance`, and I wouldn't have even asked the question. My bad. – swineone Jan 29 '19 at 14:01

1 Answers1

0

I assume you have trouble to use "var" for a singleton. You may consider use the following snippet to create a singleton and initializate some values including the observation exclusively used by the singleton:

class Singleton: NSObject {
    static private var sharedInstanceObserver : NSKeyValueObservation!

    static let sharedInstance: Singleton = {
        let sInstance = Singleton()
        sharedInstanceObserver = sInstance.observe(\Singleton.aProperty, options: .new) { st, value in
            print(st, value)
        }
        return sInstance
    }()

    @objc dynamic var aProperty = false

    func updateDoesntWork() {
        aProperty = !aProperty
    }

    func updateDoesWork() {
        Singleton.sharedInstance.aProperty = !aProperty
    }
}
staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
E.Coms
  • 11,065
  • 2
  • 23
  • 35