Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap an asynchronous method that takes a block and turn it synchronous in Objective-C

I want to wrap an async API that look like this:

[someObject completeTaskWithCompletionHandler:^(NSString *result) {

}];

into a synchronous method that I can call like this:

NSString *result = [someObject completeTaskSynchronously];

How do I do this? I did some doc reading and internet search, and attempt to use "dispatch_semaphore" to do try to achieve it like so:

-(NSString *) completeTaskSynchronously {
   __block NSString *returnResult;
   self.semaphore = dispatch_semaphore_create(0);  
   [self completeTaskWithCompletionHandler:^(NSString *result) {
       resultResult = result;
       dispatch_semaphore_signal(self.semaphore);
   }];

   dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
   return resultResult;
}

But this doesn't seem to work, it basically just halt at dispatch_semaphore_wait. Execution never reaches inside block that do the _signal. Anyone has code example on how to do this? I suspect that the block has to be on a different thread other the main thread? Also, assume I don't have access to the source code behind the async method.

like image 331
kawingkelvin Avatar asked Dec 16 '13 06:12

kawingkelvin


People also ask

Should I expose synchronous wrappers for asynchronous methods?

In my discussion of “async over sync,” I strongly suggested that if you have an API which internally is implemented synchronously, you should not expose an asynchronous counterpart that simply wraps the synchronous method in Task. Run.

When an asynchronous method is executed?

When a asynchronous method is executed, the code runs but nothing happens other than a compiler warning.


1 Answers

dispatch_semaphore_wait blocks the main queue in your example. You can dispatch the async task to a different queue:

__block NSString *returnResult;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue,^{
     result = [someObject completeTaskSynchronously];
});

Or use some other system, like NSRunLoop:

   __block finished = NO;
   [self completeTaskWithCompletionHandler:^(NSString *result) {
       resultResult = result;
       finished = YES;
   }];
    while (!finished) {
        // wait 1 second for the task to finish (you are wasting time waiting here)
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }
like image 181
Jano Avatar answered Oct 10 '22 00:10

Jano