Friday, December 14, 2007

Die, dammit

Fresh from my exploits with the scripting bridge yesterday, I figured it would be a simple matter to script the creation and deletion of a rule in the Mail app.

The code for making a new rule in Mail with the scripting bridge is beautifully concise, being a mere 9 lines of very straightforward code of the general pattern:
- Make the instance of the scripting class X, with properties P
- Get the element that is the collection of things X in the containing class (in this case the Mail app itself)
- addObject X to this collection

On the first test run I had a perfectly specified new rule in Mail (as checked by looking at the Mail preferences' rules panel).

So, removing a rule from this collection should be a doddle, right?

The scripting bridge docs talk about adding and removing objects from Element Arrays, in the section on "Using Element Arrays" in the Scripting Bridge Programming Guide for Cocoa. To add an item, you use the aforementioned 'addObject' method on the instance of SBElementArray you've acquired from the bridge (in this case, the 'rules' element of Mail). Perfect.
To remove an item from the array, there's a nice symmetrical removeObject method, and this is discussed at the end of the 'Using Element Arrays' section. Perfect (I thought).

So, my rule deletion code was written to look up the element I needed to delete (by name), and then to use the removeObject on the rules element, with the returned object from the lookup. The code was written, time to test...

The code ran, everything appeared to work in the debugger (particularly getting the element array and the lookup of the named rule object). Great. So, I go to the Mail app to check the rules in the preferences panel... oh... the rule is still there.

Instant realisation sets in that I'm doing something wrong, and that the removal of objects is not symmetrical with their addition (despite the fact that I was thinking that something in Mail would be observing the rule element array).

I figure that I'm probably not driving the Mail app properly again, or possibly this is a asymmetric pattern implicit in AppleScript itself. Oh wait... [dim recollection of AppleScript occurs]... I think you have to 'delete' objects directly, rather than removing them from elements (at least in _most_ applications).

I decide to do something I should have done at the beginning of the 'erase rule' exercise: test some actual AppleScript in Script Editor. Sure enough, the following works:

tell application "Mail"
set foo to the last item of rules
delete foo
end tell

So, my problem is basically solved I can use the delete command on the rule I've lookup up from the rules element of Mail, expressed in the scripting bridge as:
[rule delete];

Within the 'box' of the scripting bridge and its own documentation, I was suckered into the sense that deletion from an element array could be accomplished by 'removeObject' (with the unlinked object eventually being garbage collected) - but of course things don't work like that in AppleScript in general, and it's almost certainly the case that SBElementArray you actually get to deal with has nothing really to do with the copy of the elements in the application - certainly in the case of objects being deleted from it. I suppose there must be a reason why removeObject is supported on SBElementArray (perhaps if you are able to build an array up before sending the whole thing over to the target app - you might want to add and delete objects), but I'm pretty sure that its usefulness is entirely local and not to be used to actually remove objects from elements in most applications - which is the impression that is easy to form if you simply read the scripting bridge documentation without considering how AppleScript normally works.

As usual though: all's well that ends well.

Thursday, December 13, 2007

[RESOLVED] Mail Scripting Error

OK, situation normal again.

The quest for possible coding errors ensuing from the subject of my last post resulted in the following lines of investigation:
Q. Is there any way I can find what the -10000 error code means?
A. Searched (local and Google) for applescript errors and internal Mail errors: nada.

Assumption: Having checked and rechecked the code a half-dozen times, it's most likely that this is Mail saying something is wrong with its internal state. i.e. I'm driving the Script Bridge right, but setting up Mail _wrong_ (and of course I can do this because of the loose coupling I was referring to before).

Action: Recheck the Mail applescript dictionary for clues.

Result: I discovered that there are (confusingly) several kinds of 'recipient' element to the 'outgoing message' class. In fact there are: recipients, bcc_recipients, cc_recipients and to_recipients. Similarly, there are separate classes for each of these.

Having instantiated an 'outgoing message', I had been creating a 'recipient' and adding this to the 'recipients' element of the message (at which point... BOOM!). The documentation (applescript dictionary) makes no differentiation between these except in the inane style "bcc_recipient: An email recipient in the Bcc: field". I had just picked the seemingly generic 'recipient' and 'recipients' list - but on reexamination, decided that there was a chance that the correct thing was 'to_recipient' and 'to_recipients'. Sure enough... it all works now.

So, as expected, the fix was pretty trivial once the problem was correctly diagnosed. However, in these dynamic programming systems, clear documentation, clear intentions and good samples are essential. Clear (useful) errors would go a long way to make up for some deficiencies in the foregoing too, as they would at least help you narrow down to the crux of problems and quickly converge on the correct code.

Anyway... that's one more bit of experience to store away for next time.

The Yin and the Yang

One of the defining characteristics of Cocoa/ObjC is the mixing of checked and dynamic elements in the course of programming.
Ordinarily, it is good to have the compiler validate your messages to objects, and in Objective-C the compiler certainly checks whether a message is known to be understood by a given receiver. Naturally, it can only do this when it has enough information about the receiver, and in cases where it cannot determine whether the object WILL accept a message, it flags a warning.

Compiler validation is a great thing, and therefore it's best to run a 'clean' project - ensuring that the code is written in such a way as to give the compiler a chance to validate calls for you (e.g. no sending messages to something typed as 'id'!).

Yet, Cocoa makes a good deal of use of truly dynamic (runtime) loose associations. These crop up all over the place:
- Passing dictionaries (key-value collections) of attributes/properties to objects
- Key Value Coding (KVC) to address object properties
- Key Value Observing and Key Value Binding to track changes to properties, or link object properties together
- ...

Naturally, there can be no compile-time checking of the usage of services that require the passing of keys or the setting/getting of properties in this way. While such patterns are very powerful, they require discipline in use, and _importantly_ excellent documentation in order to know the right values to use dynamically and to make sense of the inevitable errors that crop up at development time (or indeed in production code).

In using Cocoa, there are definitely moments when you are reminded that you are really dealing with a very dynamic language and library. My biggest frustrations arise when some of the dynamic features simply seem to 'error out' (at runtime of course) with only the most oblique messages to suggest a fix. Because the actual problem is often detected deep in the bowels of a framework, the error message is often not particularly well framed for the code you have actually written, and the actual string you have passed in may have been parsed and transformed somewhat in the meantime (for instance a key path). Most of these errors, when they are eventually chased down, are trivial to fix. The problem is how much time it often takes to make a correct diagnosis - though admittedly this improves with experience.

At this time, I am having a frustrating moment with the new Scripting Bridge. I suppose AppleScript is one of the most indirect forms of programming, by its nature. Nonetheless, the errors you get back are often opaque or too general to really infer what is wrong.

I'm trying a simple piece of scripting of Mail through the bridge: just create and mail and send it. As far as I can tell everything is working up until I attempt to add a 'recipient' to the 'recipients' collection of an 'outgoing message'. Then I get a scripting exception, whose textual description is raw Apple Event codes and whose error number is -10000.

My first questions:
- Whose error is "-10000"? Apple Script, the bridge, or Mail? (I assume the latter)
- Where do I find the detail for this error, and will it be specific enough to help me?

I noticed with a previous problem that the laziness of the bridge could mean that it's not the actual line of code that might be the problem, but it could be the property instantiations on the objects I created a while back.

So... I feel a lot of 'flapping around' coming on in order to track this one down, with lots of experimenting and tracing and debugging. As much as I buy the power and productivity of Cocoa in general, it's this sort of diagnostic session that can really subtract from the overall feeling of productive progress.

[Watch this space for an update when I finally figure out who's not liking my code and why!]

Monday, December 10, 2007

Compound views

Playing around with view hierarchies building some Cocoa UI is pretty fun. It's great how dynamic everything is, and of course everything animates so nicely with Core Animation in Leopard (as you swap subviews for instance).

However, one surprise to me came when trying to build an encapsulated view hierarchy as a kind of reusable compound control (subtree of views). I wanted to be able to create a compound control, with a scroll view at the root and several nested views within that. I wanted to be able to encapsulate both the UI configuration itself (as a NIB of course), and be easily able to create instances of that view hierarchy (and associated controllers) within other UI - as a component.

Building the UI into a NIB is straightforward of course, but soon observed that actually deploying a compound UI in this fashion is a little 'clunky'. Assuming you want to write as little code as possible, and have the best encapsulation (for the component UI and its associated controllers), you will want to have a placeholder view of some kind in the main UI, and then have the subviews loaded and connected up under this placeholder. There are a couple of unsatisfying things about this:
1. What is this view? You are not really wanting it for any real reason other than defining a frame (rectangular extent) to load the subviews into, so it's really just a prototype with no drawing or logic of its own.
2. Yet, it probably needs to be in charge of loading the subviews from the NIB, and establishing their owner. If something tightly associated with the placeholder becomes the owner, with particular logic and responsibility for the subviews, then this somewhat breaks the encapsulation, and maybe some of the point of having separated out some of the UI.
3. If you load a top-level view from a separate NIB and add it as a subview of a pure-placeholder view in the main UI, then you have a seeming redundancy - you have a placeholder, that will only have a single top-level subview and that does nothing really useful otherwise.

These observations had me go look if there was a better way. It seemed to me that there ought to be a standard 'placeholder' view that would be able to load some view hierarchy from a separate NIB at runtime (automatically, and without bespoke logic), and then _replace_ itself in the view hierarchy with a single view from the loaded NIB. The problem of loading specific controlling logic for a view hierarchy delivered in a separate NIB seemed to be adequately fulfilled with Leopard's new NSViewController, but there was no placeholder view that you could have Interface Builder design into your user interfaces that would act as a surrogate for the runtime component.

So I felt compelled to make one.

In order to achieve the goal, you really want to be able to design the placeholder into some UI, and provide the necessary properties at runtime to tell the object where to get its NSViewController and the right NIB to load at runtime. Thankfully, in Leopard, Interface Builder 3.0 now supports plug-ins, and this sort of application is not more than a simple step or two away from the Xcode template for creating a new plug-in. Thus, the SurrogateView and its associated IB3 plug-in was born.
The only slight annoyance in the process was realising that IB3 does not currently support garbage collection, so you still have to write 'old school' to support the design-time environment, even if you can use GC at runtime. These grated a little for one whose motivation around Objective-C and Cocoa is largely underpinned by the fact that it is now a modern memory managed platform.

At runtime, when an embedded SurrogateView is unfrozen from its NIB and receives the -awakeFromNib message, it simply attempts to instantiate a new instance of an appropriate NSViewController subclass (provided as one design-time property), and provides this with the name of the NIB file (provided as the other design-time property). Then it obtains the view object from the controller, sets it to the same location and size as itself, and then it deftly replaces itself as a subview of its parent, with the view loaded from the NIB.

It all seems to work rather well, and I expect to use this plug-in in a good many places in my future UI. I've a good mind to share the SurrogateView IB3 plug-in project as freeware. It's not rocket science (by any means), but it might be useful to others in one form or another.

Page 2 (OK, I'm not going to keep the page numbering thing going after this)

As much as I'm really enjoying Objective-C 2.0 (with GC: a return to ref counting is just too retro for me even though I've used that memory management scheme for many years, though see the footnote), there are a few aspects of the language that definitely feel 'wrong' after years of Java.

Not too many, but a few.

One of the things that bug me a little is the lack of true constructor syntax, and the non atomicity of object instantiation. It's a little odd, to say the least, that the state of an object is forever threatened by the -init message (assuming something in the receiver's class hierarchy does something on receipt). While all imperative programming is reliant on calls happening intentionally, and spurious messages are bad no matter what, this seems like a bit of an extreme hole, with limited/unsatisfactory defences available.

Ignoring this, -init does allow you to do some potentially interesting things (like returning a completely different instance than the receiver), though the boiler plate code for an init definition is something of a constant price to pay for what seems like a seldom used flexibility.

----
Footnote:
Though I was forced back into this world upon the discovery that the brand new Interface Builder 3.0 is still built non-GC, and therefore any plug-ins need to be built this way. Thankfully my intended plug-in was rather simple!

Friday, December 7, 2007

Page 1

Dear diary,

I'm not a number, I'm a free man.

Now that has been established, my next project is to do something about the sneaking admiration I've had for Apple all these years (well, OK, since the return of The Steve and the adoption/emergence of Mac OS X). My career winds back to days spent with UNIX OSes (of many a favour) and back further to administering a herd of Sun 3/50 and 3/60's. So, UNIX is like an old friend - though I'm far from a CUI geek.

One of the many things generating my erstwhile respect for Apple is the almost incredible execution of innovation mixed with the pragmatism required to deliver anything. Then there's also a sense of the ergonomic that pervades more than the exterior of their products. Yes, Apple is just another company making its money and engaged in all the practices of big business - but they do have a flair about it, and both their cult and the manner of their external face makes you want to believe that they are also trying to move things forward for the benefit of human sanity(!). Regardless of all that, they are certainly a company worth watching, and that is no longer such a minority viewpoint (compared to when *I* was a switcher back in early 2000, running the early beta MacOS X on my new black G3 Powerbook).

So, with time on my hands, I am committed to playing 'catch up' with native development on Mac OS X. It's a great time to be doing this properly too, with Leopard delivering a major step forward for developers in the shape of a much improved XCode (and friends), Objective-C 2.0 with GC, and vast improvements to system and application frameworks beneath the covers.

My previous tinkering with Cocoa, a little over a year ago, was intended merely as a self-introduction - so I had some kind of clue what the animal looked like. The result was not unacceptable, though hardly of interest to anyone else (apart from the 500+ people who have evidently visited my web page!): an emulator for an ancient 8-bit British microcomputer that used Forth as the operating environment. That exercise certainly whetted my appetite. Here are a few things I found:
- I actually really liked Objective-C. I have a respect for Smalltalk, but can't be doing with weakly typed dynamic languages for anything other than component orchestration (true scripting) (i). Objective-C makes a surprisingly comfortable compromise here. Plus, it's true what Apple say about the benefits to being able to embed basic C for the purpose of addressing system layers.
- I was amazed at Cocoa. Having seriously lived with Java since literally before its 1.0, I'm a little spoiled when it comes to nice class libraries. Yet, Cocoa has a zen all of its own, and some real beauty (though this is of course in the eye of the beholder, as ever). What's even more amazing is how much of this was available back in the very early nineties with NextStep. Interface Builder and NIBs are awesome (once you "grok their metaphor"). Bindings are very powerful, though prone to frustration as form of symbolic loose coupling. In other areas, I find the libraries a little minimalistic - providing the bare bones, but lacking some convenience (such as the lack of rectangle manipulation functions).
- MacOS X has so much elegance. It's big, and very 'layered', with some redundancies through both the layering (BSD, Core, App foundations) and history. Nevertheless, there are many aspects of its design that are highly commendable, and moreover, compare very favourably to the 'alternatives'. For instance, I love the way bundles work (applications, frameworks), the "xcopy install", the way preferences and configuration is handled. Every system is a compromise, but Mac OS X has many mechanisms that deliver robustness and encapsulation, without too much added complexity. I suppose I'm inclined to think about DLL hell and the wonder that is the Microsoft repository in terms of a contrast.
- The Apple documentation is variable. To be fair, it's quite voluminous and what's there is solid (and I appreciate how hard it is to keep solid, correct, documentation). However, there's much of it that is too terse. The reference material often elides important information that indicates intended usage, and although there is clearly an intent to provide this in the various guides and samples, there are big holes. This has been the cause of much too much "suck it and see" experimentation to my personal tastes (some of this is always necessary to learn, of course). For instance, trying to understand the code patterns to use the new NSCollectionView was a challenge, though perhaps this is differentiated by being brand new, with the collateral only just emerging.

So, now I want to get into a bit more of a serious relationship with this platform and development environment. The learning challenges stand ahead with Core Data next. Should be fun...

------
(i) In fact, I am the progenitor of a strictly typed lazy functional language, CAL, developed with colleagues at Business Objects.