Sunday, January 13, 2008

More dynamic coolness... I think

I have a requirement to force the order of some items in my UI according to a property value. These objects being sorted are CoreData NSManagedObjects and, as such, have no formal order - you have to assert this yourself. Binding to a Core Data Managed Object Context from an NSArrayController is a Thing of Beauty, but now I need to assert the order of my objects in the UI. So, naturally, I need to connect the NSArrayController Sort Descriptors binding to a sort descriptor (actually an array of NSSortDescriptors) that will cause the right ordering of its arrangedObjects.

Now, I figure:
1. That I will always want a particular ordering enforced
2. That I don't know where to construct and keep this array of NSSortDescriptors, because this is entirely a UI thing. I could create a property of a reachable object (via NSApplication) that is initialised to this array, but that's not very satisfactory.

It occurs that what I really want to do is to have an instance of the appropriate sort descriptors right there in the NIB - and of course this is exactly what one can do with the Object Inferface Builder component.

But first, I have to arrange to have an instance of an object that IS the right sort descriptor. Without any Interface Builder plugin jiggery-pokery to inject an instance with a particular value set as a property, this is best served by creating a new class that always sets up the appropriate sort descriptor for a particular key and order (e.g. descending).

Forgetting for a moment that what I needed was an array of sort descriptors, I created a subclass of NSSortDescriptor whose -init: sets up the appropriate sort specification. An instance in the NIB was then easily created by dropping in an Object component from the palette, and setting its class to my new class. A binding can easily be made to this object from the relevant NSArrayController in the same NIB.

Then I remembered: "Oops, I need an array of these things, even if it only contains the one sort descriptor object". Mmmm, should I change my class to construct an private NSArray with 'self' as the only element (given that I only wanted the one descriptor and I had made the class a subclass of NSSortDescriptor)? I could then have a property on the object that exposes the array which is what I'd bind to from the NSArrayController. Though, perhaps this shouldn't really be an NSSortDescriptor if I add this allusion of containment?

Another thought occurred. Given the wonders of 'duck typing' I could just have my subclass of NSSortDescriptor masquerade as an array, by implementing -count: and -objectAtIndex: . In which case, -count: would ALWAYS return 1, and -objectAtIndex: would return 'self' for index 0, or raise an NSRangeException otherwise.

I decided, mostly out of curiosity, to try the array masquerading - and this appears to work perfectly.

However, having achieved this, I'm left pondering about best practices. Clearly a lot of Objective-C/Cocoa's dynamic dispatch is to be embraced. Moreover, if one is to fully avail oneself of all the features (including KVO and KVB), then dynamic references via key paths is inevitable. However, there remains a question of appropriate patterns in code you write: it can certainly be misleading for an object instance that advertises itself as being of one class to suddenly know how to behave in entirely different circles. I wonder what the perceived wisdom is in the Cocoa community around such matters (having just arrived myself). Is this just a matter of diligent documentation (and isn't typing almost just a form of documentation in truly dynamic object systems?)

In this particular case, despite the compactness of having a single instance handle what it means to be an NSSortDescriptor (legitimately perhaps making it a subclass of this class) as well as what it means to be an array of exactly one of these objects, it might be considered better form to fully advertise the array qualities, even though they are not really the major concept. More importantly one could argue that even though I currently have no intention of every needing multiple NSSortDescriptors in the array (for primary, secondary, etc. sorting), I may one day need to change that constraint - whereupon it would be much better to have a class that advertises that it returns an array of sort descriptors rather than providing this almost covertly through the happenstance of implementing methods that make the object look like an array to others.

Perhaps I should spend some time looking at best practices where such conundrums have existed for decades: Smalltalk. Although in modern times, I suspect such problems can be better dealt with by the overt documentary power of traits, there must have been many occasions where, like this occasion, there was no protocol to announce "this object has array-like behaviour" despite inheriting from class X. It would be interesting to analyse what choices were made in these situations and which (with hindsight) were considered malodorous!

No comments: