On Apr 17, 2017, at 05:40 , Jean-Daniel <mail...@xenonium.com> 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 <cocoa...@charlessoft.com> 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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to