Forget the Code

Along with the rest of the world, I’ve been coding on Snow Leopard recently. Blocks are a great addition to the C language, irrespective of their use in Apple’s Grand Central Dispatch APIs. I’ve found one usage-pattern where they save me time (I have little to spare), reduce the amount of mundane monkey-work I have to do , and make my code more reliable. In many places blocks can save me writing a whole line of code.

Transaction-based APIs

Cocoa is full of APIs that cause the developer to start a transaction, do some work and then end the transaction. Take for example drawing something with a shadow in an NSView:


- (void)drawRect:(NSRect)rect;
{
    [[NSColor whiteColor] setFill];
    NSRectFill([self bounds]);
    [NSGraphicsContext saveGraphicsState];
    [[self shadow] set];
    [[NSColor redColor] setFill];
    [[NSBezierPath bezierPathWithRect:NSInsetRect([self bounds], 10, 10)] fill];
    [NSGraphicsContext restoreGraphicsState];
    // do some more drawing
}

The important part of the above code is between the calls to save and restore the graphics state. Quartz (the underlying drawing API) is a state-machine, so if we were to set the shadow on our existing context, all subsequent drawing would be performed with said shadow. By saving the state, we can set a shadow temporarily and the continue to draw without a shadow by restoring the graphics context to its eariler state. It doesn’t stop with shadows, though; there are a multitude of setting we can apply to our contexts.

If you forget to call +[NSGraphicsContext restoreGraphicsState] after saving a state though then that makes me a sad panda. Try flipping the context’s transformation matrix and not restoring your state. You won’t win any design awards.

We have similar issues if we are manually notifying key-value observers of changes. All calls to -[NSObject willChangeValueForKey:] must be bracketed with calls to -[NSObject didChangeValueForKey:].

It doesn’t stop there, if you’ve done any work with Core Animation, its not uncommon to want to disable the implicit animations of layers, or to make a group or animations with longer durations than default. In these cases your code is littered with:


[CATransaction begin];
[CATransaction setDisableActions:YES];
// change any number of properties on your CALayers
[CATranaction commit];
This gets old, very quickly.

Enter Blocks

Lets make our code more robust, we all like robust. Blocks are anonymous functions, we can write them in-line and the can capture variables from their enclosing scope. In real-person terms we can write methods like this:

@implementation NSGraphicsContext (ESAdditions)
+ (NSGraphicsContext *)transactionUsingBlock:(void (^)(NSGraphicsContext * /* currentContext */))block;
{
    [self saveGraphicsState];
    block([self currentContext]);
    [self restoreGraphicsState];
}
@end

In our app we can then do things like this:

- (void)drawRect:(NSRect)rect;
{
    [[NSColor whiteColor] setFill];
    NSRectFill([self bounds]);
    [NSGraphicsContext transactionUsingBlock:^(NSGraphicsContext *currentContext){
        [[self shadow] set];
        [[NSColor redColor] setFill];
        [[NSBezierPath bezierPathWithRect:NSInsetRect([self bounds], 10, 10)] fill];
    }];
    // do some more drawing
} 

We’ve now done some drawing with a shadow, and we’ve not had to write those pesky calls to +[NSGraphicsContext saveGraphicsState] and +[NSGraphicsContext restoreGraphicsState].

Readability++

The wonderful side-effect of this is that the code executed as part of the block is indented by Xcode, too. Which means we can see, at a glance, the scope to which our transaction applies. Imagine doing the same for drawing into a CGLayer, your drawing code is in the block, and it all gets rendered into the layer. Blocks help you expresses your intent. (Yes, that’s a loaded statement. Feel free to quote me on this.).

You can download my categories and functions here. With a shiny New BSD license. Enclosed I have categories on:

  • NSGraphicsContext
  • CGContextRef
  • NSManagedObject (for writing your custom accessors)
  • NSObject (for KVO)
  • CATransaction
  • NSAnimationContext
  • NSTextStorage

Mutexes and semaphores would also be a great candidate for this. You can easily write your own equivalent of @synchronized, to work directly with pthread mutexes.