Search code examples
swiftprotocolskey-value-observing

Swift KVO on an object conforming to a protocol


I have a protocol (X) and a class (A) that implements protocol X:

protocol X, NSObjectProtocol {
   var toBeObserved: MyCustomClass? { get}
}

class A: NSObject, X {
   var toBeObserved: MyCustomClass?
   ...
}

in another class I want to observe the variable toBeObserved:

class B {
    ...
    var instanceConformingToX: X <-note: not A but simply the protocol X
    ...
    func someFunc() {
       self.observation = self.observe(\.instanceConformingToX.toBeObserved) { (observed, change) in
       ...
       }
    } 
}

}

Everything piece of the equation here is or conform to NSObject, so I expect to be able to KVO toBeObserved but instead I get a runtime crash:

Fatal error: Could not extract a String from KeyPath Swift.KeyPath<MyAppName.B, MyFramework.A>

Thanks.


Solution

  • Make sure to mark the observed property as @objc and dynamic. As Using Key-Value Observing in Swift says:

    Mark properties that you want to observe through key-value observing with both the @objc attribute and the dynamic modifier.

    The protocol and participating classes will need to be marked @objc, too. E.g.:

    class MyCustomClass: NSObject { ... }
    
    @objc protocol X: NSObjectProtocol {
        @objc dynamic var toBeObserved: MyCustomClass? { get }
    }
    
    class A: NSObject, X {
        var toBeObserved: MyCustomClass? = MyCustomClass()
    }
    
    class B: NSObject {
        @objc var x: X = A()
    
        var token: NSKeyValueObservation?
    
        func addObserver() {
            token = observe(\.x.toBeObserved) { object, _ in
                print(object)
            }
        }
    }