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.
So happy to hear about the bibtex support! Oh, the animated views will be nice too, and thanks for sharing the example code.