Having recently rewritten a ~350 line (i.e. rather simple) class to use GCD heavily, it's safe to say that while it's very nice, it's definitely not trivial. The class is now about 450 lines and most of those are different than the previous version; it also took a week or so of debugging various issues including writing a stress test that just hammered on it from 1000 threads at once until something went wrong.
GCD is very efficient, useful, and removes a lot of the boilerplate of queue-based concurrency and asynchronicity, but it doesn't magically make concurrent programming easy.
Although it's not at all intuitive from looking at it, GCD encourages two different patterns (that I'm aware of so far). One is the actor model, any mutable shared state in the program should be associated with a queue (actor) and interacted with only by passing blocks (messages... the similarity breaks down here a bit; they're more like continuations in this use) to that queue. The other is the more standard data parallel model.
For example, the standard synchronous accessor pattern:
Code:
- (NSArray *) thingies {
return [thingies copy]; //avoid returning mutable internal state directly; I'm assuming garbage collection, so this isn't a leak
}
turns into something like (pardon my naming):
Code:
- (void) snapshotSharedThingiesThen:(void ^(NSArray *))continuation {
dispatch_async(fooQueue, ^ {
NSArray *snapshot = [thingies copy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
continuation(snapshot)
});
});
}
and the code using it goes from:
Code:
for (id thingy in [something thingies]) {
/* do some horrendously slow operation with each thingy that the rest of the program doesn't really care about*/
[thingy doWork];
}
to:
Code:
[something snapshotSharedThingiesThen:^(NSArray *thingies) {
//-enumerateObjectsWithOptions:usingBlock: is a fairly thin layer on top of dispatch_apply()
[thingies enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:void (^)(id obj, NSUInteger idx, BOOL *stop) {
/* do some horrendously slow operation with each thingy that the rest of the program doesn't really care about*/
[obj doWork]; //note that -doWork must be threadsafe
}];
}];
We've gone from synchronous and serial to asynchronous and parallel, but at the cost of totally restructuring how the code worked. In order to get results back to the UI we would "message" it using dispatch_async to the main queue (via dispatch_get_main_queue()), or if absolutely necessary, block using dispatch_semaphore, dispatch_sync, or dispatch_groups.