Your program is exiting before the block has a chance to run.
The nature of an asynchronous call like dispatch_async() is that it returns and allows the caller to continue on, possibly before the task that was submitted has completed. Probably before it has even been started.
So, +blocksTest returns to the call site in main(). main() continues to its end (by the way, without returning a value, which is bad for a non-void function). When main() returns, the whole process exits. Any queues, tasks, worker threads, etc. that GCD was managing is all torn down during process termination.
The process does not wait for all threads to exit or become idle.
You could solve this by calling dispatch_main() after the call to +blocksTest in main(). In that case, though, the program will never terminate unless you submit a task which calls exit() at some point. For example, you could put the call to exit inside the block you create in +blocksTest.
Actually, in this case, because the task would run on a background thread and not the main thread, anything which delays the immediate exit would be sufficient. For example, a call to sleep() for a second would do. You could also run the main run loop for a period of time. There's no period of time that's guaranteed to be enough that the global queue has had a chance to run your task to completion, but in practical terms, it would just need a fraction of a second.
There's a complication in that methods to run the run loop exit if there are no input sources or timers scheduled. So, you'd have to schedule a bogus source (like an NSPort). As you can tell, this is a kludgy approach if you're not otherwise using the run loop for real stuff.
application:didFinishLaunchingWithOptions:method, you'll see expected result