I am struggling to use the new strongly-typed KVO syntax in Swift 4 to observe properties that are only visible through a protocol:
import Cocoa
@objc protocol Observable: class {
var bar: Int { get }
}
@objc class Foo: NSObject, Observable {
@objc dynamic var bar = 42
}
let implementation = Foo()
let observable: Observable = implementation
let observation = observable.observe(\.bar, options: .new) { _, change in
guard let newValue = change.newValue else { return }
print(newValue)
}
implementation.bar = 50
error: value of type 'Observable' has no member 'observe'
let observation = observable.observe(\.bar, options: .new) { _, change in
Clearly, Observable
is not an NSObject
. But I cannot simply cast it to NSObject
, because the type of the keypath will not match the type of the object.
I tried being more explicit about the type:
let observable: NSObject & Observable = implementation
But:
error: member 'observe' cannot be used on value of protocol type 'NSObject & Observable'; use a generic constraint instead
let observation = observable.observe(\.bar, options: .new) { _, change in
Is what I am trying to do not possible? This seems a common use case. It is easily done with old #keypath syntax. Can you offer any alternatives? Thanks.
This code compiles and runs in Swift 4.1.2 (Xcode 9.4):
import Foundation
@objc protocol Observable: AnyObject {
var bar: Int { get }
}
@objc class Foo: NSObject, Observable {
@objc dynamic var bar = 42
}
let implementation = Foo()
let observable: NSObject & Observable = implementation
func observeWrapper<T: NSObject & Observable>(_ object: T) -> NSKeyValueObservation {
return object.observe(\.bar, options: .new) { _, change in
guard let newValue = change.newValue else { return }
print(newValue)
}
}
let observation = observeWrapper(observable)
implementation.bar = 50
withExtendedLifetime(observation, {})
All I did is wrap the call to observe
in a generic function that's constrained to NSObject & Observable
.
Despite the introduced generic, it’s still possible to pass the protocol-typed observable
to this new function. To be honest, I can't really explain why this works.
Edit: This might explain it: protocols in Swift generally don't conform to themselves, so it wouldn't be allowed to call a generic function with an existential (a protocol type) even if the constraints match. But there's an exception for @objc
protocols (without static requirements), which do conform to themselves. I suppose this works because Observable
is marked @objc
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With