Wednesday, September 30, 2009

Writer's block

In my early days with Cocoa (using PyObjC) one of the hardest things to wrap my head around was the byzantine nature of the way you ask the user for a filename to save to (or open). What can be difficult about that?

The function has one of the longest names I've ever seen:

- (void)beginSheetForDirectory:(NSString *)path file:(NSString *)name modalForWindow:(NSWindow *)docWindow modalDelegate:(id)modalDelegate didEndSelector:(SEL)didEndSelector contextInfo:(void *)contextInfo

The modal part isn't bad---it just means we'll stop everything else for the user (in that application anyway) until they please answer the <frickin'> question. The modalDelegate and contextInfo can be nil. Great!

The complication came with selectors (what are they?) and how do we get one in Python? Suddenly I had to learn about method signatures and other arcanery when all I wanted to do was save something...

Now, there's a new world: blocks.

The old method is deprecated (that's Cocoa for "it sucks to be you"). The new method defined in the docs is:

- (void)beginSheetModalForWindow:(NSWindow *)window completionHandler:(void (^)(NSInteger result))handler

It is shorter, but WTF? What is that caret-like thing sticking up there? What's with the double parens "))" and all the brackets? That's a block. It's like a Python lambda (section 4.7.5). Also known as a closure. And most informatively: an "anonymous function."

In my code, it looks something like this:



    [savePanel beginSheetModalForWindow:[NSApp mainWindow]
completionHandler:^(NSInteger result) {
if (result == NSOKButton) {
[savePanel orderOut:self];
[self application:NSApp
saveFile:[savePanel filename]];
}
}];


What this:

(void (^)(NSInteger result))handler

says (in the Cocoa docs, first example) is the following. When the modal dialog is finished, there will be an integer result that describes which button the user clicked. We are going to pass into the modal dialog handling part of Cocoa a "completionHandler".

What is that? It is a function with no name that we are going to define on the spot. It doesn't return anything (and we can leave that out in our implementation), but it expects an NSInteger argument named result. The next five lines (including the first curly bracket) are the code for our function, followed by another curly bracket that terminates the block, and followed (finally) by a straight bracket that ends the message to the savePanel.

Sneakily, we call the method to actually save the file from within this anonymous function that takes a simple NSInteger and returns nothing.

That's the basic explanation. Blocks are apparently way more useful than that. But this, you have to know.