Re: Translating KVO-ed property to Swift
Re: Translating KVO-ed property to Swift
- Subject: Re: Translating KVO-ed property to Swift
- From: Quincey Morris <email@hidden>
- Date: Mon, 17 Apr 2017 11:09:44 -0700
On Apr 17, 2017, at 05:40 , Jean-Daniel <email@hidden> wrote:
> This is a good practice, but I don’t think this is required for computed property, especially if you take care of willChange/didChange manually, as the OP does.
Here is what the Swift interoperability documentation says (https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html):
> "You can use key-value observing with a Swift class, as long as the class inherits from the NSObject class. You can use these three steps to implement key-value observing in Swift.
>
> "1. Add the dynamic modifier to any property you want to observe. […]”
Here is what the Swift language documentation says (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html):
> “dynamic”
>
> "Apply this modifier to any member of a class that can be represented by Objective-C. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched using the Objective-C runtime. Access to that member is never inlined or devirtualized by the compiler.”
That is, unless you specify “dynamic” there’s no *guarantee* that invocations to the property accessors will use obj_msgSend, and since there’s no way in Swift to guarantee that obj_msgSend *won’t* be used for the property, the outcome for automatic KVO is unpredictable.
On Apr 17, 2017, at 08:07 , Charles Srstka <email@hidden> wrote:
>
> // Note that this doesn’t need to be dynamic, since we are not relying on Cocoa’s built-in automatic swizzling,
> // which is only needed if we are not calling willChangeValue(forKey:) and didChangeValue(forKey:) ourselves.
> @objc var version: String {
> willSet {
> // Send the willChange notification, if the value is different from its old value.
> if newValue != self.version {
> self.willChangeValue(forKey: #keyPath(version))
> }
> }
> didSet {
> // Send the didChange notification, if the value is different from its old value.
> if oldValue != self.version {
> self.didChangeValue(forKey: #keyPath(version))
> }
> }
> }
I tested what happens (in Swift 3.1, Xcode 8.3.1) using this code:
> private var versionContext = 0
>
> class ViewController: NSViewController {
> @objc /*dynamic*/ var version: String = “” {
> willSet {
> if newValue != self.version {
> self.willChangeValue (forKey: #keyPath(version)) }
> }
> didSet {
> if oldValue != self.version {
> self.didChangeValue (forKey: #keyPath(version)) }
> }
> }
> override func viewDidLoad () {
> super.viewDidLoad ()
> addObserver (self, forKeyPath: #keyPath(version), options: [], context: &versionContext)
> }
> override func observeValue (forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
> print ("observedValue for \(version)")
> }
> @IBAction func buttonClicked (_ sender: Any?) { // There’s a button in the UI hooked up to this
> version = version == "" ? "1" : "\(version)"
> }
> }
This version of the code (with “dynamic” commented out) displays the observer message once, as desired, and then not again, as desired. Uncommenting “dynamic” causes the message to be displayed twice the first time, and then once more every subsequent button click.
So, Charles’s approach *appears* to work, because the “version” property isn’t participating in automatic swizzling. However, it’s subtly wrong because there’s no way to prevent other source code from leading the compiler to *deduce* that the method is dynamic. Once that happens, there’s an extra unwanted notification every time the property is set.
And again, in the converse scenario (automatic KVO, where you want notifications unconditionally) the “dynamic” keyword isn’t optional.
The correct solution, I claim, is to replace the declaration of “version” with this:
> static func automaticallyNotifiesObserversOfVersion () -> Bool { return false }
> @objc dynamic var version: String = “” { … }
and then use either Charles’ or Jean-Daniel’s logic to generate the notifications manually as desired.
(BTW, the “@objc” is currently redundant, but will soon become required, via SE-0160 <https://github.com/apple/swift-evolution/blob/master/proposals/0160-objc-inference.md>.)
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden