Wednesday, February 23, 2011

Practical Design Patterns with Blocks and Grand Central Dispatch

Introduction

When Mac OS X 10.6 was introduced, the Mac got a very powerful duo of developer tools that made development on a lot easier. With iOS 4.0 developers finally got access to these tools as well. These tools are known as Blocks & Grand Central Dispatch. This article is not an introduction to these technologies as i've already covered them before here http://cocoasamurai.blogspot.com/2009/09/guide-to-blocks-grand-central-dispatch.html. What this article is, is a introduction to some design patterns I use and have seen other developers use in 1 handy article.
Many of the design patterns I am displaying here are actually in use in my Zangetsu Framework which is open source under the MIT License on Github.
Iterative Callbacks
/**
 Ruby inspired iterator for NSArray in Objective-C
 */
-(NSArray *)cw_each:(void (^)(id obj))block
{
    for(id object in self){
        block(object);
    }
 
    return self;
}
Blocks are really good for doing callbacks and one of the things you can do already is enumeration. Now while Objective-C is definitely expressive with high level english like syntax, sometimes it is unnecessarily verbose in my opinion. When I started reading through my Ruby 1.9 book one thing I loved was ruby's enumeration with each. It's so simple and while it could be slightly more verbose it's succinct enough. So I decided to implement each in Objective-C with blocks since I could actually make it work. The result is what you see above.
Additionally you can use grand central displatch and make this asynchronous.
-(NSArray *)cw_eachConcurrentlyWithBlock:(void (^)(id obj))block
{
    dispatch_group_t group = dispatch_group_create();
 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
    for(id object in self){
  
        dispatch_group_async(group, queue, ^{
            block(object);
        });
    }
 
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_release(group);
 
    return self;
}
In this example I am using dispatch groups and getting a reference to a global queue and then basically enumerating over the objects and then calling dispatch_group_wait() to make sure that all the blocks complete. This type of thing is great when you want to be both asynchronous, but also ensure that the method itself is synchronous.
/**
 Simple mapping method using a block
 */
-(NSArray *)cw_mapArray:(id (^)(id obj))block
{
    NSMutableArray *cwArray = [[NSMutableArray alloc] initWithCapacity:[self count]];
 
    for(id obj in self){
        [cwArray addObject:block(obj)];
    }
 
    return cwArray;
}
Here is a good example of putting that callback capability to good use. In this example I wanted to map one array to another one. This is basically exactly the same as my cw_each method above except that inside this method I am creating an array that the new (mapped) array is being stored in and returning an object from the block. If you wanted to you could be selective in mapping an array to another by simply checking that the object returned from the block is not nil and only then adding it to an array you are mapping to.
Enclosing Blocks
Another clever use for blocks is to enclose them in between other code for various purposes. For example in this case I was experimenting with only executing a block when I am building debug code. So instead of doing...
#ifdef DEBUG
    NSString *myString = ...
    //do something with string
#endif
I think this is more clear and less cluttered
//in .h
typedef void (^DebugBlock)(void);
void CWInDebugOnly(DebugBlock block);

//in .m
void CWInDebugOnly(DebugBlock block)
{
#ifdef DEBUG
    block();
#endif
}
This way in code I can do
CWInDebugOnly(^{
    NSString *myString = ...
    //do something with string....
});
This relies on DEBUG being defined only in your debug builds. I have this set up to only be defined in my Debug and Analyze configurations. I've also seen Mike Ash cleaverly do this exact same thing with NSAutoreleasePools like so...
//in .h
typedef void (^VoidBlock)(void);
void inAutoreleasePool(VoidBlock block);

//in .m
void inAutoreleasePool(VoidBlock block)
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    block();
    [pool drain];
}
Completion Blocks
Completion Blocks are especially good things to have around. They are very useful on objects that are performing some operation, usually asynchronously, and we would like to know when the operation has been completed. We may or may not care about the implementation details, but we need a mechanism to do something when this is done. This is where completion blocks are excellent helpers. In the past before blocks we may have used notifications or delegates and created methods especially to deal with this 1 specific application. Now it's as easy as assigning a block as a variable and executing it when you are done. One prime example of this is NSOperation. You can create it, add it to a queue, and go on and do other things, and once the operation is done it executes your completion block. These completion blocks are also excellent because we can do these things on threads in grand central dispatch and simply in the completion block dispatch back to the main thread and update the user interface
NSData *data = //..data from a file or internet
MyOperation * operation = [[MyOperation alloc] initWithDataToParse:data];

[operation setCompletionBlock:^(NSString *result){
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self updateUIWithString:result];
    }];
}];

[queue addOperation:operation];
This code is easy to read and follow what it is doing. Let's see how we can do this in our classes.
//.h
typedef void (^TaskCompletionBlock)(void);

@interface MyClass : NSObject {}
@property(nonatomic,copy) TaskCompletionBlock block;
-(void)doTask;
@end

//.m
@implementation MyClass

@synthesize block;

-(id) init
{
    self = [super init];
    if(self){
        //
    }
    return self;
}

-(void)doTask
{
    //do some task here...

    if(self.block) {
        self.block();
    }
}

@end
Dispatch_Once
Out of all the API calls I saw in grand central dispatch the one that initially impressed me least was dispatch_once. However after reading its documentation and using it a bunch I realized how useful this one API call is. Not only it is a dead simple way to ensure that code only gets executed once, but it is also thread safe. From the API...
"If called simultaneously from multiple threads, this function waits synchronously until the block has completed."
This means its perfectly safe to run code like this from multiple threads...
+(NSString *)coreDataKeyNameForWebAPIKey:(NSString *)name
{
    static dispatch_once_t dictPrec;
 
    dispatch_once(&dictPrec, ^{
  
        coreDataKeyMappings = [[NSDictionary alloc] initWithDictionary:NSDICT(
            kCDEntityNameKey,           kWebEntityNameKey,
            kCDEntityDateKey,           kWebEntityDateKey,
            kCDEntityTimeStampKey,      kWebTimeStampKey,
            KCDEntityUpdateKey,         kWebEntityUpdateKey)];
    });
 
    NSString *translatedKey = [coreDataKeyMappings valueForKey:name];
 
    return translatedKey;
} 
Conlcusion
Grand Central Dispatch and Blocks are just awesome. Even just blocks by itself makes many things possible that weren't before. With grand central dispatch and blocks many problems that have been traditionally associated with multithreading go away. These 2 technologies don't eliminate all these problems, but they certainly do make them much more manageable.

9 comments:

  1. Your "cw_mapArray:" example is returning an owned object but shouldn't be.

    ReplyDelete
  2. @dave actually I designed it to return an array that way. In a retain environment I would agree with you and perhaps I should even note that, but I designed my code to be used in a garbage collected environment where this isn't a big issue.

    ReplyDelete
  3. @colin but in a garbage-collected environment, isn't [autorelease] a no-op? I thought the idea was that proper reference-counted cocoa code would generally work without change in a garbage collected environment, ensuring that you could write the code once and use it in either environment.

    Or to put it another way, it's possible to write code that will only work in a garbage-collected environment, but code that works correctly in a reference-counted environment should automatically work in both.

    ReplyDelete
  4. @daniel
    your correct code written with retain/release should work just fine in garbage collected environments. When I started designing the apps I am working on right now I decided that I wasn't going to write retain/release code and instead write code designed specifically to be used just in garbage collection since I was writing new Mac OS X apps and wasn't sharing any code with iOS projects.

    If I wanted to it really wouldn't be hard to adapt my code for retain/release.

    ReplyDelete
  5. Thanks for sharing these wonderful tricks Colin. I'm not sure but you may have a bug in code:

    @property(nonatomic,copy) TaskCompletionBlock *block;

    The asterisk should be omitted:

    @property(nonatomic,copy) TaskCompletionBlock block;

    Or maybe you use some cool technique. Please advise.

    ReplyDelete
  6. @vadim
    yes your right. d'oh! It's fixed now

    ReplyDelete
  7. Anonymous4:08 PM

    If I may ask, what benefit do you see using the block syntax over #ifdef syntax with the DEBUG example? Maybe I’m just used to using #ifdef but using block seems to create more code text with no inherent benefit?

    Maybe I’m not correctly picturing how this looks in implementation with lots of #ifdefs in use?

    ReplyDelete
  8. @Anyonymous

    the point of the code encapsulating the #ifdef is that it makes the code a bit cleaner and can free you from having to remember some details of the #ifdef statements. I guess you either like it or don't. I do use the CWInAutoreleasePool(^{ }) function a bit because it means not having to constantly create pools and release them in retain/release environments.

    ReplyDelete
  9. iljawascoding11:17 AM

    @ Colin,

    Thanks for the post.

    Have you checked the performance of cw_eachConcurrentlyWithBlock with larger arrays (1000+ objects)?
    Even with simple blocks like ^{ [stringA isEqualToString: stringB];} it's already slower than a single threaded approach.

    Maybe calling dispatch_group_async for each item in the array isn't the best choice.

    Regards,
    Ilja

    ReplyDelete