2009 09 218 ways to use Blocks in Snow Leopard
1 Custom sorting To sort an NSArray
in a custom way, you need to write a new method and pass its selector to the sorting function. With blocks, just sort in-place :
id sortedArray = [myArray sortedArrayUsingComparator: ^(id a, id b) { if (a.someValue > b.someValue && a.otherValue > b.otherValue) return NSOrderedAscending; ... some more sorting code }];
2 Enumeration Looking for an object in an array can be done with a block enumerator. Set stop
to YES
to stop enumeration.
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { NSLog(@"obj %@", obj); if (...terminatingCondition...) *stop = YES; }];
This is the least interesting use of blocks. The for .. in
is much more readable :
for (id obj in myArray) { if (...terminatingCondition...) break; }
The one advantage enumerateObjectsUsingBlock:
has is the current index. enumerateObjectsWithOptions:usingBlock:
lets you go backwards.
3 Just-this-scope functions You need to call a piece of code multiple times but don't feel like adding a new method to your class just to accomplish this one task. For instance, protocol_copyMethodDescriptionList(protocol, isRequired, isInstance, count)
needs multiple calls to enumerate all protocol methods. Instead of creating a new method, use a block :
- (void)enumerateProtocolMethods:(Protocol*)p { // Custom block, used only in this method void (^enumerate)(BOOL, BOOL) = ^(BOOL isRequired, BOOL isInstance) { unsigned int descriptionCount; struct objc_method_description* methodDescriptions = protocol_copyMethodDescriptionList(p, isRequired, isInstance, &descriptionCount); for (int i=0; i<descriptionCount; i++) { struct objc_method_description d = methodDescriptions[i]; NSLog(@"Protocol method %@ isRequired=%d isInstance=%d", NSStringFromSelector(d.name), isRequired, isInstance); } if (methodDescriptions) free(methodDescriptions); }; // Call our block multiple times with different arguments // to enumerate all class, instance, required and non-required methods enumerate(YES, YES); enumerate(YES, NO); enumerate(NO, YES); enumerate(NO, NO); }
4 Common cleanup I trust we've all done this :
- (id)veryFragileDaisyChain { ... init 1 if (condition1) { ... init 2 if (condition2) { ... init 3 if (condition3) { ... init 4 if (condition4) { // At last ! Our shiny new object ! } ... init 3 cleanup } ... init 2 cleanup } ... init 1 cleanup } }
How about a block ?
- (id)veryFragileDaisyChain { // Let's declare everything in advance __block id obj1 = nil, obj2 = nil, obj3 = nil; // Cleanup block void(^cleanup)() = ^{ if (obj3) ... cleanup if (obj2) ... cleanup if (obj1) ... cleanup }; // Onward. ... init code 1 if (!condition1) return cleanup(), nil; ... init code 2 if (!condition2) return cleanup(), nil; ... init code 3 if (!condition3) return cleanup(), nil; ... init code 4 if (!condition4) return cleanup(), nil; // Done. return shinyNewObject; }
5 Functional style Nicolas seriot wrote about functional code in ObjC before Snow Leopard was out, hinting at using blocks to implement functional style code in ObjC. Strangely enough, Snow Leopard didn't implement these. NSArray
does have a indexesOfObjectsPassingTest:
method, but it returns a NSIndexSet
instead of an array ?! With blocks, we can implement a filter
method that will create a new array collecting objects matching our block condition.
@implementation NSArray(FunctionalStyle) - (NSArray*)filter:(BOOL(^)(id elt))filterBlock { // Create a new array id filteredArray = [NSMutableArray array]; // Collect elements matching the block condition for (id elt in self) if (filterBlock(elt)) [filteredArray addObject:elt]; return filteredArray; } @end
This removes the need to loop over array element ourselves when we only want to extract elements. Just supply a condition and voilà ! A new array.
// Return a new array containing elements greater than 4 id filteredArray = [myArray filter:^(id elt) { return [elt intValue] > 4; }];
6 List comprehensions are a shorthand way of creating arrays.
// This shorthand Javascript var s = [2*i for (i in 100) if (i*i>3)] // is equivalent to var newArray = [] for (var i=0; i<100; i++) if (i*i>3) newArray.push(2*i)
We can implement the same with blocks, just a little more verbosely :
@implementation NSArray(ListComprehensions) // Create a new array with a block applied to each index to create a new element + (NSArray*)arrayWithBlock:(id(^)(int index))block range:(NSRange)range { id array = [NSMutableArray array]; for (int i=range.location; i<range.location+range.length; i++) [array addObject:block(i)]; return array; } // The same with a condition + (NSArray*)arrayWithBlock:(id(^)(int index))block range:(NSRange)range if:(BOOL(^)(int index))blockTest { id array = [NSMutableArray array]; for (int i=range.location; i<range.location+range.length; i++) if (blockTest(i)) [array addObject:block(i)]; return array; } @end
Then call with a block and a range, and an optional condition :
// Just block and range id newArray = [NSArray arrayWithBlock:^(int i) { return [NSNumber numberWithInt:i*2]; } range:NSMakeRange(5, 10)]; // Optional condition : keep only multiples of 3 id newArray = [NSArray arrayWithBlock:^(int i) { return [NSNumber numberWithInt:i*2]; } range:NSMakeRange(5, 10) if:^(int i) { return i%3 == 0; } ];
7 Async processing This is a simple piece of Grand Central Dispatch : enqueue an asynchronous job. The function will return immediately while the block performs in the background in another thread. Use performSelectorOnMainThread:
to update your GUI afterwards.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ // Some looong computation in here ... // [NSThread isMainThread] tells us that calling [self taskDone] is done with the dispatch thread // Use this to call our end of job notification on the main thread [self performSelectorOnMainThread:@selector(taskDone) withObject:nil waitUntilDone:NO]; });
8 Async monitoring Another piece of GCD, lifted from the Concurrency Programming Guide.
You can use GCD to monitor asynchronously resources. In this example, a dispatch source is set to watch when a file is renamed. One block handles notification, another handles common cleanup.
// Create a dispatch source and start watching it - (dispatch_source_t)watchFileRename:(NSString*)fileName { int fd = open([fileName UTF8String], O_EVTONLY); if (fd == -1) return NULL; [fileName retain]; // Cleanup block void(^cleanup)() = ^{ close(fd); [fileName release]; }; // Create source dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_RENAME, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); if (!source) return cleanup(), NULL; // Dispatch source handler dispatch_source_set_event_handler(source, ^{ // *** Async event handler *** // Called when the file was renamed. }); // Dispatch source destructor dispatch_source_set_cancel_handler(source, ^{ cleanup(); }); // Start watching dispatch_resume(source); return source; } // Release a dispatch source - (void)releaseDispatchSource:(dispatch_source_t)source { if (!source) return; dispatch_source_cancel(source); dispatch_release(source); } // Start watching file rename. Call releaseDispatchSource when done. dispatch_source_t source = [self watchFileRename:@"/FileToWatch"];There are even more uses of blocks, as Grand Central Dispatch uses them extensively to perform concurrent tasks. I encourage you to read the Concurrency Programming Guide.
A good list. You missed the concurrency options on the Foundation collections enumeration methods though, which some will find very convenient.
NSDictionary's enumeration methods provide an additional convenience; it passes both the key and the value for every entry in the dictionary. No more enumerating over the keys, then getting the values separately.
NSIndexSet also has a very useful enumeration method now. No more need to use the awkward -firstIndex and -indexGreaterThanIndex: approach.
There are also two NSString methods: enumerateSubstringsInRange:options:usingBlock:, which will let you enumerate lines, paragraphs, composed character sequences, words, or sentences, and the convenience method, enumerateLinesUsingBlock:.
Also, you have an error in #4. obj1-obj3 need to be __block variables. Otherwise, the block just captures the pointer values at the point where it is created.