Blocks in Objective C

written by Martin HĂ€cker on

I've long had a fascination with SmallTalk style blocks in Objective-C. So much so, that I learned a lot about how C and GCC work when I implemented them on the primitive of GCCs nested functions myself.

[browser:open-source/closures-in-objc/trunk Heres the Source]

Of course, just as I had it working, Apple deprecated GCCs nested functions, as they where implemented using a trampoline on the stack. And of course, a trampoline being executable code they where out when the non executable stack came in.

Ah well.

BUT, Apple just released with Snow-Leopard a new compiler feature [Blocks]!

Yay, closures in C!

So here's how it looks if you implement the Smalltalk collection iteration protocoll in ObjC. (Note: this of course are not propper ObjC-Names, but each Smalltalker will none the less get a tear in their eye when they see this)

#import <Foundation/Foundation.h>

@implementation NSArray (BlocksTest)

- (void) do:(void (^)(id))aBlock;
{
    // Take care, -enumerateObjectsUsingBlock: wraps an auto-release pool around the iteration
    [self enumerateObjectsUsingBlock:
        ^(id obj, NSUInteger idx, BOOL *stop) {
            aBlock(obj);
        }];
}

- (NSArray *) collect:(id (^)(id))aBlock;
{
    id collectedItems = [NSMutableArray arrayWithCapacity:[self count]];
    [self do:^(id each) {
        [collectedItems addObject:aBlock(each)];
    }];
    return [collectedItems copy]; // REFACT: consider to drop copy
}

- (id) detect:(BOOL (^)(id))aBlock;
{
    // Take care, -enumerateObjectsUsingBlock: wraps an auto-release pool around the iteration
    __block id resultObject = nil;
    [self enumerateObjectsUsingBlock:
        ^(id obj, NSUInteger idx, BOOL *stop) {
            if (aBlock(obj)) {
                resultObject = obj;
                *stop = YES;
            }
        }];
    return resultObject;
}

- (id) detect:(BOOL (^)(id))aBlock ifNone:(id (^)())errorBlock;
{
    id foundElement  = [self detect:aBlock];
    if (foundElement)
        return foundElement;
    else
        return errorBlock();
}

- (id) inject:(id)aValue into:(id (^)(id, id))aBlock;
{
    // Need to take care with retain here, because apple wraps an auto-release pool around the block iterator. :/
    __block id collected = [aValue retain];
    [self do:^(id each){
        collected = [aBlock([collected autorelease], each) retain];
    }];
    return [collected autorelease];
}

- (NSArray *) reject:(BOOL (^)(id))aBlock;
{
    id selectedObjects = [NSMutableArray arrayWithCapacity:[self count]];
    [self do:^(id each){
        if (aBlock(each))
            return;
        [selectedObjects addObject:each];
    }];
    return [selectedObjects copy]; // REFACT: consider to drop copy
}

- (NSArray *) select:(BOOL (^)(id))aBlock;
{
    return [self reject:^(id each){ return (BOOL) ! aBlock(each); }];
}
@end


#define log(objcObject) fprintf(stdout, "%s\n", [[objcObject description] UTF8String])

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    id array = [NSArray arrayWithObjects:@"first", @"second", @"third", nil];

    log(@"\ndo:");
    [array do:^(id each){
        log(each);
    }];

    log(@"\ncollect:");
    log([array collect:^id(id each){
        return [each uppercaseString];
    }]);

    log(@"\ndetect:");
    log([array detect:^(id each){
        return [each isEqual:@"second"];
    }]);

    log(@"\ndetect:ifNone:");
    log([array detect:^(id each){ return NO; } 
                        ifNone:(id)^{ return @"Yeehaw!"; }]);

    log(@"\ninject:into:");
    log([array inject:@"" into: ^ id (id concatenation, id element){
        return [concatenation stringByAppendingString:element];
    }]);

    log(@"\nreject:");
    log([array reject:^(id each){
        return [each hasSuffix:@"nd"];
    }]);

    log(@"\nselect:");
    log([array select:^(id each){
        return [each hasSuffix:@"d"];
    }]);

    [pool drain];
    return 0;
}

Ain't that pretty?

[browser:open-source/smalltalk-like-iterators/trunk Here's the current version!]