Re: Basics of Cocoa Bindings
Re: Basics of Cocoa Bindings
- Subject: Re: Basics of Cocoa Bindings
- From: Alex Hall <email@hidden>
- Date: Fri, 11 Sep 2015 23:41:13 -0400
Minor update: in the zip file, in ViewController.swift, I hadn't changed the variable name in the for loop. It should be "tweets", not "tweetsArray". The project builds, and the error appears, either way. Actually, that it builds with a mistake like that seems a bit odd. Anyway, just something to watch if anyone decides to have a look at the file.
> On Sep 11, 2015, at 23:20, Alex Hall <email@hidden> wrote:
>
>
>> On Sep 11, 2015, at 21:04, Quincey Morris <email@hidden> wrote:
>>
>> On Sep 11, 2015, at 15:49 , Alex Hall <email@hidden <mailto: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.
>
> Okay, the error I'm seeing (about [ViewController count]) is the table asking for how big it should be. That makes way more sense--I was under the impression that the array controller was asking on the table's behalf, that's why I expected it to use countOfTweets().
>
> My question was more about the array controller knowing if it's hooked to an array or a scalar, but I get what you're saying now. I wonder, though, just what I've improperly configured that makes this not work? Specifically, shouldn't the array controller be helping the table get the count it needs, instead of the table sending that message directly to the view controller? Here's a zip file of my project as it stands now, in case it helps:
> https://dl.dropboxusercontent.com/u/17005121/Bindings Test.zip
>>
>> 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)
>>
>
>
> --
> Have a great day,
> Alex Hall
> email@hidden
>
> _______________________________________________
>
> 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
--
Have a great day,
Alex Hall
email@hidden
_______________________________________________
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