Re: Basics of Cocoa Bindings
Re: Basics of Cocoa Bindings
- Subject: Re: Basics of Cocoa Bindings
- From: Quincey Morris <email@hidden>
- Date: Sat, 12 Sep 2015 01:04:33 +0000
On Sep 11, 2015, at 15:49 , Alex Hall <email@hidden> wrote:
>
> I followed all of that, and I see what you're saying. The binding to an array is, to it, no different than binding to a boolean or a string, it's just a single object. But how does a binding know what it's accessing? I figured it was down to the type of object being bound to; bind to an array, and you've got a 1-to-many, bind to, say, an integer, and you've got a 1-to-1. It sounds like it's more in what methods the view controller (in my case) implements.
It’s all about context. Each UI element (KVC “client”) knows what kind of relationship it’s expecting. A text field needs a scalar value, so it expects a one-to-one relationship to be named by the property. A table view needs row data, so it expects an indexed one-to-many relationship.
When a client asks for information via KVC, the message that gets sent may or may not be specific to a particular kind of relationship. So you actually have to start breaking things down into cases.
For a text field that wants to retrieve a model value (a scalar), it uses the KVC method ‘valueForKey:’ (or possibly ‘valueForKeyPath:’, which is a convenience method in KVC that breaks a key path into individual keys, and invokes ‘valueForKey:’ sequentially). KVC figures out how to retrieve the value, and does things like transforming a numeric property value into a NSNumber object, since everything has to be an object for KVC.
For a table view that wants to retrieve (say) the tweet for row 20 of the table, it uses the KVC method … surprise! … ‘valueForKey:’, with “tweets” as the key. The object that KVC returns is — conceptually — an array *proxy* object. That is, it’s an Apple-provided object that responds to all of the NSArray behavior, but knows how to translate each NSArray method into an access to the real array that’s backing the property — or the various custom accessors that provide values, if there’s no real array.
So the table view ends up with a NSArray-like object, and it can use ‘objectAtIndex: 19’ to find the object in the 20th row. The reason there’s a proxy involved here is to allow the model class to avoid *constructing* an array to return (a potentially expensive operation), while letting the client use the full power of the full NSArray API. That’s why, if the table view wants to know the number of rows, it’s going to send a ‘count’ message rather than a ‘countOfTweets’ method.
In short, KVC has leveraged the existing complexity of the NSArray class API to provide a rich choice of ways to access indexed one-to-many property values, without having to re-invent an entirely new set of analogous API methods.
Things are a little more complicated for changing values in the model instead of retrieving them. For scalars — that is, in contexts where the client needs a one-to-one property, the KVC method it calls is ‘setValue:forKey:’. This usually gets translated into a 'setXxx:’ method invocation, the accessor you expect. It’s worth noting that if this setter is the *only* way that the property way can be changed, then you get automatic KVO compliance for the property, which is a Good Thing™.
If a table view needs to modify (say) the tweet for row 20, it does *not* use ‘setValue:forKey:’. Functionally, it *could* do that, and KVC would do the right thing, but that would require the table view to construct an entire array of tweets, which is horribly expensive. Instead, it would really like to use something like a proxy object, so that the elements that are unchanged don’t cost anything to keep unchanged. This is exactly what happens. The table view calls ‘mutableArrayValueForKey:’ to get what’s called a “mutable proxy”. As retrieved, it represents the current model values using the NSMutableArray API, so it allows the table view to use the ‘replaceObjectAtIndex:withObject:’ on the proxy to make just that simple update.
The other cool thing that the mutable proxy does is modify the data model in a KVO compliant way. It does this by translating the NSArray API method into a series of invocations of your custom accessor methods (such as the insert… and remove… methods). As with one-to-one properties, if you always modify a one-to-many property via the mutable proxy, then the property is automatically KVO compliant.
It should be clear, if you think about this, why you want to avoid using NSArray-valued one-to-one properties. KVC and its clients can’t tell the difference between this (the value is an object which happens to an NSArray subclass) and one-to-many properties (the values are the contents of the proxy objects, not the proxy objects themselves). Doing the wrong thing with the object as value vs. the object as temporary packaging is going to wreck your application logic pretty fast.
Now, for one-to-many properties, there’s another wrinkle. When getting values, since the proxy is read-only, if you have an actual array as backing store, then the actual array works perfectly well as its own KVC “proxy". Only in special cases do you need an actual proxy for read-only purposes, and an actual proxy is awkward because the NSArray proxy object that KVC is designed to provide is more or less impossible to get hold of in a Cocoa app written to modern code conventions.
But there’s more, but this time I’ll switch to a second post before I get moderated instead of after.
(continued in part 2)
_______________________________________________
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