Rixstep
 About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Home » Learning Curve » Developers Workshop

Sheets & Mavericks

Add it to the list.


Get It

Try It

Apple OS X sheets are a wonderful thing. They're also beyond the comprehension of programmers coming from other platforms. One need only remember the clumsy way the Firefox programmers dealt with them in the nascent ports to the platform. Sheets are document-specific, but sadly none of the unwashed platforms available know anything about 'document-specific'. Documents are separate from applications - something the copycats at Microsoft never grokked, and open source of course followed the Microsoft lead, and so the OS world is where it is today. Save with OS X.

Sheets are dialogs attached to document windows. Not application windows. Document windows. It's been the cause for liberating lulz to watch snarky pundits complain that Apple menus are not on the application windows, showing they completely miss the point. As but one example.

Running a sheet, as running a dialog, is tricky business. And when it comes to selecting paths through standard I/O mechanisms, things are just as tricky. The following method has been in use since the days of NeXT.

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

beginSheetForDirectory might look daunting, but it's not. It's fairly easy. All one has to do is program the code. A file panel (NSSavePanel or its descendant NSOpenPanel which doesn't allow setting the actual file name) needs a directory to start in, needs a file name, needs to know what window it's blocking, who's in charge of the code, and most importantly: what to do when the sheet closes.

The sheet will come out of the title bar of the document window and disappear the same way.



The code pointed to by didEndSelector will look like this.

-(void)savePanelDidEnd:(NSSavePanel *)sheet
        returnCode:(int)returnCode
        contextInfo:(void *)contextInfo;

returnCode will indicate if the sheet was closed with an 'OK' or something else. And the contextInfo is what you pass to the invocation in the API's contextInfo. As that data is typed as void *, you can pass anything you want - an object, a pointer to a struct, anything.

It's fairly simple. The key hurdle is in understanding that control cannot pass back to the code that invoked the sheet. So you need somewhere else to pick it up.

That code's worked well ever since the NeXT years. NeXT 'merged' with Apple in 1997, so the code's at least sixteen years old. Add to that the time it was in use at NeXT, for Apple got the object-oriented interface from NeXT (and paid $429 million for it).

So things are hunky-dory, right? And this article has no purpose whatsoever, right? It's just another meaninglessly meandering piece of 'Mac iPod iPhone iPad iOS Mac community' blogging, right?

Absolutely.

Deprecated!

Apple deprecated beginSheetForDirectory with the release of 10.6. Why? Good luck trying to find out. What does 'deprecated' mean? Good question again. For the method's still alive and well - knock on wood - in 10.9 Mavericks. So what does 'deprecated' mean? Evidently it means 'we might remove this later just like we change the threading on screws used in our hardware but then again we might not, so good luck'.

Joe Mac Fanboy might have a single application available in Apple's walled garden. He might call beginSheetForDirectory a single time. Joe Mac Fanboy can afford to make a single change in his code. But what about all of Joe Mac Fanboy's clients? What happens to them when their software suddenly ceases to work?

This is one area where Microsoft always get it right and Apple stubbornly insist on getting it wrong. beginSheetForDirectory works fine. It's worked fine from the beginning, from before NeXT bailed out Apple. It continues to work well to this day. So you don't deprecate it and you don't destroy old good code and piss your customers and third party developers off.

Apple have a new method to replace the 'deprecated' beginSheetForDirectory. And it's a doozy. It's no better than beginSheetForDirectory. In fact it's worse, even though it might seem easier for 'some'. So why introduce it? As Apple are notoriously tight-lipped about everything, one must guess. In an educated fashion. And the guess is it's that contextInfo parameter that's spooking the new generation of Apple programming wizards. IBM won't fix code that works...

Seriously now: what do you do if you've got gobs of data you want to send to didEndSelector? Why you'd have to construct a struct, allocate memory for it, pass a pointer to it, then let didEndSelector see if it's non-zero on entry, and in such case free the memory when it's no longer needed. That seems like a lot of work! Ouch! OUCH!

But you never have gobs of data - it's a bloody path creation routine. So that's funny. That's really funny.

But let's not let trivialities like that stop us when we're on the road to glory. For there's a far easier way. Let's redefine the entire Objective-C language instead, and thereby redefine the underlying C language as well - and let's make a big mess of things just to make sure we all have fun.

C code is oriented around the block. A block is something enclosed in curly braces. Blocks give code their scope. Automatics declared inside a block are not visible outside. Automatics declared inside a block are set up behind a new stack pointer when control flow enters the block.

And so forth.

Functions are blocks too. But functions have I/O. Functions can take arguments and can return values. Blocks cannot. The closest thing to this is BCPL's valueof, but BCPL saw its best days before Ken Thompson looked it over for B. If you have a block and you want it to have I/O, then you make it a function. And functions by definition cannot be nested in C. (First clue, first warning light.) Overuse of blocks is wasteful. It's wasteful in terms of code bulk and operational efficiency.

Here's what those geniuses at Apple dreamt up.

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

Ponder the syntax for a moment. What is that? Good question. The geniuses at Apple don't seem quite sure themselves. That's going to be the first block of code ever used in C that's going to need a semicolon to end it. It's the first piece of code in C that's going to be its own declaration and definition at the same time. Go Apple.

void (^handler)(NSInteger) = ^(NSInteger code) {
};

[Just for fun: take away that ending semicolon and watch how Apple's compiler burps. It's not a NOP - it's part of the syntax. And be sure to look closely at the diagnostic their compiler issues.]

So how do you use it? This is interesting too, for it seems to defy all rules of control flow. Essentially what you do is preface your invocation code with the completion handler, and then, as if by a force of nature beyond your control, control flow skips past the handler (by magic) to your invocation code, then jumps back again when the sheet exits.

As one piece of code:

{
    void (^handler)(NSInteger) = ^(NSInteger code) {
    };

    -(void)beginSheetModalForWindow:(NSWindow *)window
            completionHandler:(void (^)(NSInteger result))handler
}
  1. Control flow passes the left curly brace.
  2. Because it's an Apple compiler, it can magically skip the completion routine.
  3. Control flow continues to beginSheetModalForWindow and the sheet opens.
  4. The sheet closes and the 'block' you skipped first time around now gets called.
  5. This code (the completion handler) will sooner or later reach its right curly brace.
  6. But again, and as if by magic, control flow skips past the sheet invocation code.
  7. Your Apple control flow finally makes it past the sole official right curly brace.

Magic.

[The astute reader will also notice that the new method lacks provisions for setting the initial path (directory and file name). This can, as always, be done by directly invoking the receiver class, but it's another indication of how those wizards at Apple think, for passing arguments on the stack is one thing and switching contexts hysterically is another. Super-fail.]

No Perks

Are there any perks to this new brilliant interface? No. The generated code is actually bulkier. Which is not surprising as they're mucking about with the innards of the language itself. And that's a crime. They're not mucking about with APIs: they're mucking about with a language definition. And as Objective-C by definition is supposed to build on C - with no intrusion or corruption - they're also corrupting the bridge between C and Objective-C and even threatening to corrupt the underbody of C itself.

This is not atypical Apple, but it's wrong, so wrong. It's possible there's provision for this in the current C11 standard (if anyone wants to delve that deep) but that doesn't change the fact that it's wrong. And it certainly doesn't change the fact that miles upon miles of legacy code in the hands of programmers and just as much software in the hands of users may at any time, on the whim of Apple, cease to function because someone somewhere found the age-old NeXT mechanism too much of a challenge.

The current C language standard C11 defines the block:

A block allows a set of declarations and statements to be grouped into one syntactic unit. The initialisers of objects that have automatic storage duration, and the variable length array declarators of ordinary identifiers with block scope, are evaluated and the values are stored in the objects (including storing an indeterminate value in objects without an initialiser) each time the declaration is reached in the order of execution, as if it were a statement, and within each declaration in the order that declarators appear.

There doesn't seem to be any mention of syntax approaching Apple's completion handlers. Note that the above applies as well to the block that encloses the 'Apple' code with the completion handler and its invocation. Oops.

So one just adds this to all the other anomalies that are turning up. Such as the NSText implementation that Safari and Preview dropped, such as the leaky 'Lion' 10.7/10.8 code, such as the versioning (revision) system, such as...

But OS X is still the world's most advanced operating system, right? Maybe. At least it has sheets.

Postscript: Sheets & the ACP

All ACP code - and this includes the Xfile System - has been updated nonetheless. Tests of a preliminary release of 10.9 Mavericks indicate the legacy API is still in place. Numerous additional changes have been made to the ACP, including the Apple penchant for expressing everything in the local file system in terms of URLs rather than paths, the concomitant deprecation of additional APIs, and the at times unwieldy quagmires resulting from the inconsistent application of this stillborn idea.

ACP code that's 'Mavericks-ready' has been rolled out to registered users.

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.