30 August 2011 by Published in: Uncategorized 1 comment

I was in a situation where I was calling a method that was calling a method that was calling a method that:

  1. Was calling dispatch_async and scheduling the start of a network request
  2. Doing all manner of low-level networking code on various threads
  3. Completing all the low-level stuff and executing a block all the way up in the caller with the response of the network request

While this is a fine async architecture, it would be useful to construct a convenience “synchronous” wrapper so that you can call a function to do the network request that dispatches all this nonsense and just spins and waits for the result and returns the result to the caller.  In other words, it reduces to the problem of building dispatch_sync out of dispatch_async.  However, this is a lot more complicated than it sounds.  The naive implementation:

  1. Create a block that calls the real block and at the conclusion of its execution sets a flag
  2. dispatch_async the block in #1
  3. Spin waiting for the flag to be set
  4. Return

The problem is, how do we do that “spin” step?

  1. You can’t use NSTimer or performObject:…withDelay because all of these methods would require exiting your current function call.  But you need to return a value to the caller at the time you return, so you can’t return until the network request is complete.
  2. You can’t use dispatch_sync because that would be begging the question.  Back in our original algorithm, we’re doing a ton of crazy network threading in step #2, far more than will fit in a single synchronous block.
  3. Obviously you can’t just spin with sleep(1) while the network code runs.  Less obviously, you can’t spin with CFRunLoopRun either, which appears to do absolutely nothing.  I naively assumed that running the main run loop would allow dispatch events on the main queue, but this appears not to be the case.

The solution is buried deep in the manpage for dispatch_async (of all places!).  It reads:

Conceptually, dispatch_sync() is a convenient wrapper around dispatch_async() with the addition of a
semaphore to wait for completion of the block, and a wrapper around the block to signal its completion.

Aha, dispatch semaphores!  It turns out that dispatch_semaphore_wait will spin in such a way as to allow dispatch events while waiting for the signal.  The final code block:

-(void) beginSynchronousRequestWithPrefix:(NSString*) uprefix
{
    void (^myBlock)(NSObject*)  = [self.block retain];
    dispatch_semaphore_t holdOn = dispatch_semaphore_create(0);
    self.block = ^(NSObject * obj) {
        myBlock(obj);
        dispatch_semaphore_signal(holdOn);
    };
    dispatch_async(self.block...)
    dispatch_semaphore_wait(holdOn, DISPATCH_TIME_FOREVER);
    [myBlock release];
}

Want me to build your app / consult for your company / speak at your event? Good news! I'm an iOS developer for hire.

Like this post? Contribute to the coffee fund so I can write more like it.

Comments

Add comment

Copyright © 2011 Drew Crawford, All Rights Reserved
Powered by WordPress