1

My questionwas about if it was possible to use KVC on a Singleton property on Swift. I was testing KVC on a class was able to get it working but decided to see if it work on a Singleton class.
I'm running into an error stating that the "shared" property of my Singleton isn't KVC-compliant.

 class KVOObject: NSObject {
    @objc static let shared = KVOObject()
    private override init(){}

    @objc dynamic var fontSize = 18
 }

 override func viewDidLoad() {
    super.viewDidLoad()

    addObserver(self, forKeyPath: #keyPath(KVOObject.shared.fontSize), options: [.old, .new], context: nil) 
 }

 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
   if keyPath == #keyPath(KVOObject.shared.fontSize) {
      // do something
   }
 }

I am currently getting the error below:

NetworkCollectionTest[9714:452848] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ addObserver: forKeyPath:@"shared.fontSize" options:3 context:0x0] was sent to an object that is not KVC-compliant for the "shared" property.'

victorydub
  • 111
  • 10

1 Answers1

4

The key path is not correct. It’s KVOObject.fontSize. And you need to add the observer to that singleton:

 KVOObject.shared.addObserver(self, forKeyPath: #keyPath(KVOObject.fontSize), options: [.old, .new], context: nil)

As an aside, (a) you should probably use a context to identify whether you're handling this or whether it might be used by the superclass; (b) you should call the super implementation if it's not yours; and (c) make sure to remove the observer on deinit:

class ViewController: UICollectionViewController {

    private var observerContext = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        KVOObject.shared.addObserver(self, forKeyPath: #keyPath(KVOObject.fontSize), options: [.new, .old], context: &observerContext)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if context == &observerContext {
            // do something
        } else {
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }

    deinit {
        KVOObject.shared.removeObserver(self, forKeyPath: #keyPath(KVOObject.fontSize))
    }

    ...
}

Or, if in Swift 4, it's now much easier as it's closure-based (avoiding need for context) and is automatically removed when the NSKeyValueObservation falls out of scope:

class ViewController: UICollectionViewController {

    private var token: NSKeyValueObservation?

    override func viewDidLoad() {
        super.viewDidLoad()

        token = KVOObject.shared.observe(\.fontSize, options: [.new, .old]) { [weak self] object, change in
            // do something
        }
    }

    ...
}

By the way, a few observations on the singleton:

  1. The shared property does not require @objc qualifier; only the property being observed needs that; and

  2. The init method really should be calling super; and

  3. I'd probably also declare it to be final to avoid confusion that can result in subclassing singletons.

Thus:

final class KVOObject: NSObject {
    static let shared = KVOObject()

    override private init() { super.init() }

    @objc dynamic var fontSize: Int = 18
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • The Swift 4 version is actually not working when observing a singleton's property. – HuaTham Apr 05 '18 at 06:35
  • thank you for your reply. I inspected my code carefully and yes it turns out that Swift 4's KVO for singleton works just fine. – HuaTham Apr 05 '18 at 16:21
  • I also found that you don't even need the `@objc` keyword when you declare static singleton property. So `static let shared = KVOObject()` would work. You only need `@objc dynamic` for the observed properties. – HuaTham Apr 05 '18 at 16:22
  • 1
    Correct, that's unnecessary (e.g. see https://stackoverflow.com/a/25219216/1271826 for general discussion on KVO and Swift). His singleton has other issues, too, which I've added to the end of my answer. – Rob Apr 05 '18 at 17:34