Re: Translating KVO-ed property to Swift
Re: Translating KVO-ed property to Swift
- Subject: Re: Translating KVO-ed property to Swift
- From: Charles Srstka <email@hidden>
- Date: Thu, 20 Apr 2017 12:24:07 -0500
> On Apr 19, 2017, at 9:12 PM, Quincey Morris <email@hidden> wrote:
>
> On Apr 19, 2017, at 15:49 , Charles Srstka <email@hidden> wrote:
>>
>> Cocoa automagically does its secret subclass thing to wrap the setter and call the didChange/willChange calls for any property you didn’t tell it not to do. It needs the property to be dynamic for this to work.
>
> Yes, that’s almost exactly what I said**. But it still doesn’t make the “dynamic” keyword an on/off switch. Again, the KVO notifications aren’t activated *because* the method has the Swift-specific dynamic attribute, but because the method uses Obj-C dispatching. The “dynamic” keyword [currently] forces the method to use Obj-C dispatching, but the reverse isn’t true. In the absence of the keyword, there’s nothing formally stopping the compiler from using Obj-C dispatching if it chooses to.
>
> At some level, though, I’m prepared to stipulate that it’s a distinction without much practical difference.
The statement didn’t say anything about “dynamic” being an “on/off switch.” The statement that you were quibbling was that you can mark a stored property as ‘dynamic’, and Cocoa will do the rest. It didn’t say anything about what *causes* it, it was meant to be a practical guide to how to implement KVO properties in Swift. And to make Cocoa automatically handle the didChange/willChange calls, you mark a property as dynamic. I mean, yes, we could go all the way down to explaining how everything works in terms of the physics and chemistry of electrons and semiconductors, but that wouldn’t be very practical, would it?
>> I should add that if a computed property needs ‘dynamic’ in order for its notifications to fire, the property is not properly KVO-compliant.
>
> It’s impossible to say in general what counts as a change to a mutable property. Indeed it’s perfectly possible for a property to exist for the purpose of generating KVO notifications without having any meaningful value, and there are plenty more un-generalizable considerations:
The entire purpose of KVO is to monitor changes to values. The word “value” is literally right there in the title, as well as most of the major APIs that make it work. observeValue. willChangeValue. didChangeValue. value(forKey:). setValue. keyPathsForValuesAffecting. Using KVO without a meaningful value is, frankly, using a hammer to drive in a screw. For a value-less notification, something like NSNotificationCenter is a much better choice.
> — It’s up to an individual property (with a meaningful value) to decide whether KVO notifications are issued when the setter is called (in effect, the default case) or when the value actually changes (as in Rick’s original code), or under some other conditions (e.g. I can imagine a property limiting the rate of notifications using a timer).
Which does not require a property to be dynamic, and in fact would require *disabling* Cocoa’s use of dynamism for the property, via returning false for automaticallyNotifiesObserversOf.
> — There’s no general presumption whether the value returned by the getter is synchronized with the value passed to the setter.
The setter, however, is likely to make some kind of change to the underlying storage such that we will be notified of it. And if no such change is made, then there is little point in sending change notifications.
> — There’s no general presumption whether the value returned by the getter is synchronized with the notification, in a multi-threaded environment.
Assuming you’re referring to the fact that something in another thread could change the property in the middle of your observer, this still has little that I can see to do with the ‘dynamic’ keyword.
> — There’s no general presumption that KVO notifications originate in the property's setter at all, or in the setter only.
>
> Etc. “keyPathsForValuesAffecting…”-style dependencies are just a convenient short-cut to a specific, simple, typical behavior.
Who said there was?
The bottom line with KVO notifications is that you’ll get one when either:
1. willChange and didChange get called for the property you’re watching, or
2. willChange and didChange get called for something that the property you’re watching is dependent on.
In case 1, you can either call willChange or didChange yourself, or you can let Cocoa do it for you. In Swift, you need to mark it ‘dynamic’ for the latter to happen.
In case 2, you don’t need willChange or didChange to get called for your property, because we will already get notified when the dependency’s willChange and didChange get called. In fact, we might not want them to, because it’ll cause the notification to double-post—once in the setter, and then again in the dependency’s setter, as in the example below:
> import Foundation
>
> class Foo: NSObject {
> @objc dynamic var bar: String = ""
>
> @objc static let keyPathsForValuesAffectingBaz: Set<String> = [#keyPath(bar)]
> @objc dynamic var baz: String {
> get { return self.bar }
> set { self.bar = newValue }
> }
>
> private var barContext = 0
> private var bazContext = 0
>
> override init() {
> super.init()
>
> self.addObserver(self, forKeyPath: #keyPath(bar), options: [], context: &barContext)
> self.addObserver(self, forKeyPath: #keyPath(baz), options: [], context: &bazContext)
> }
>
> override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
> if context == &barContext {
> print("bar changed: \(self.bar)")
> } else if context == &bazContext {
> print("baz changed: \(self.baz)")
> } else {
> super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
> }
> }
> }
>
> let foo = Foo()
> foo.baz = "Baz"
When you run the code above, you get three notifications: one that baz changed, one that bar changed, and then another that baz changed. Without the ‘dynamic’ on baz, you’ll get its notifications only once when it’s set from Swift. Now, depending on how important it is to you to avoid these duplicates, you might want to add an automaticallyNotifiesObserversOfBaz to make sure this behavior doesn’t occur in *any* case. However, regardless of whether you want to go that far, it makes little sense to go out of one’s way to add something specifically to *enable* the double-posting behavior. Thus, there is no need for ‘dynamic’ on this property.
Charles
_______________________________________________
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