24

How can I make a block execute synchronously, or make the function wait for the handler before the return statement, so the data can be passed back from the block?

-(id)performRequest:(id)args
{
__block NSData *data = nil;   

    [xyzclass requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
        data = [NSData dataWithData:responseData];
    }];

    return data;
}
Sathya
  • 2,299
  • 4
  • 19
  • 25
  • edited to make a more specific code snippet – Sathya Jul 07 '11 at 07:14
  • at any point of time if you come across a need to make async to sync there is something terribly wrong with your design. – Kunal Balani Jan 17 '14 at 16:44
  • Just curious, why do you want to do so? If you just want the completion of the block else where then your "performRequest" method should also include a block that should be called inside the internal block completion. as Kunal pointed out there is something wrong with your design as you want to do it. – Rohit Kumar Mar 04 '16 at 11:49

6 Answers6

29

You can use semaphores in this case.

-(id)performRequest:(id)args
{
    __block NSData *data = nil;   
     dispatch_semaphore_t sem = dispatch_semaphore_create(0);
     [xyzclass requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
       data = [NSData dataWithData:responseData];
       dispatch_semaphore_signal(sem);
     }];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    return data;
}

semaphore will block execution of further statements until signal is received, this will make sure that your function does not return prematurely.

dev gr
  • 2,139
  • 1
  • 18
  • 33
  • 1
    This is the correct answer, assuming that you are not executing on the main thread. – Gene Z. Ragan Oct 16 '15 at 03:03
  • This approach should only be used with asynchronous methods that run and call completion on a separate thread other than calling thread. – dev gr Mar 12 '20 at 07:45
  • Note: If you are on the main thread you can still use semaphore, it just has to be modified slightly, see here: https://stackoverflow.com/a/4326754/2057171 – Albert Renshaw Jun 06 '20 at 21:14
3

async is almost always better. but if you want synchronous:

-(id)performRequest:(id)args
{
__block NSData *data = nil;   

    [xyzclass requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
        data = [NSData dataWithData:responseData];
    }];

    while(!data) {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    }
    return data;
}

Disclaimer: CocoaCouchDeveloper says that of course this will only work if the completion block and the runloop are on the same thread. I assumed that because many(most) COMPLETION handler I know work that way but of it is valid in principle.

The above is not thread safe
use a semaphore or something maybe.
also I said I don't promote this

Daij-Djan
  • 47,307
  • 15
  • 99
  • 129
  • disclaimer: written inline. [may not be 100% but clearly shows what you have to do] – Daij-Djan Dec 17 '13 at 00:19
  • Can you assure that the access to variable _data_ is thread safe and involves memory barriers? I doubt it is. If there are no memory barriers, the compiler is eligible to keep _data_ in a register and thus, _data_ gets never updated in the calling thread. – CouchDeveloper Jan 02 '14 at 16:55
  • Well, actually, it is only reliable IFF the completion handler executes on the same thread where the run loop is executed. I just made a simplified test, and it turned out: the data variable will be saved in a register which gets never updated (and the while loop will never return). There are also no memory barriers, which means, if data would be updated, access to it is not guaranteed to be atomic/safe, and the pointer value returned might be crippled. But: Objective-C will prevent the compiler to use many optimization opportunities and chances increase that it works anyway ;) – CouchDeveloper Jan 02 '14 at 17:44
  • The OP's question is quite general. We should never assume a completion handler is executed on the main thread, unless it is explicitly documented. In fact, executing a handler on a "well known" thread is bad practice since it increases chances of dead locks. There are a few exceptions where it makes sense to execute the handler on the main thread: UIViews animations for example. AFN does not belong to this group, tough - even though it uses the main thread. In contrast, in AVFoundation handlers will use a private thread (see "Concurrent Programming with AV Foundation"). – CouchDeveloper Jan 03 '14 at 10:13
2

You could just add a method which processes the returned data and call that in your block:

-(void)performRequest:(id)args{
    __block NSData *data = nil;   

    [xyzclass requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
        data = [NSData dataWithData:responseData];
        [self processData:data]; //method does something with the data 
    }];
}
redisant
  • 51
  • 1
  • 4
-1

You can do synchronous request in another thread like below code

-(void)performRequest:(id)args
{

 NSURLResponse *response = nil;
 NSError *error = nil;
 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
 data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}

From Main thread you can call this

[self performSelectorInBackground:@selector(performRequest:) withObject:args];

or else you can do asynchronous request using following method

[NSURLConnection alloc]initWithRequest:request delegate:self];

and implement delegate methods for NSURLConnection

iMOBDEV
  • 3,567
  • 2
  • 19
  • 33
  • what if i need to access a class that has no alternative than to use a block, for example : [classxyz requestAccessToAccountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error){}]; has no alternatives, correct me if i am wrong – Sathya Jul 07 '11 at 07:07
  • you can implement delegate methods as I said in answer, control will come in connectionDidFinishLoading: on success of completion of request, if you are requesting from server – iMOBDEV Jul 07 '11 at 07:17
-1

are you sure you want to do it synchronously ? if yes, you can call (or put) your handler function in your block or use Jignesh advice (and use “performSelectorInMainThread” when your handler is finished and you want to return values.

the asynchronous way is (a little bit) harder, but better as: - it forces you to write clean code (no passing of convenient variables) - you can execute other thing so the users won't wait and think your app is slow.

you should really give it two or three hours to go asynchronous. small pain for full gain. you can also have a look to Key-Value Observing.

teriiehina
  • 4,613
  • 2
  • 39
  • 62
-3

You could do like this.

-(id)performRequest:(id)args
{
__block NSData *data = nil;   

 [xyzclass performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse          *urlResponse, NSError *error) {

     dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{

     data = [NSData dataWithData:responseData];

     });

 }];

return data;
}
Ilanchezhian
  • 17,378
  • 1
  • 49
  • 55
  • i would like to see a nice simple tutorial to understand this because so far, tutorials are too basic and useless, such as pragmatic studio's multiplier example, or too obscure to be useful. Do you know of any? – marciokoko Feb 01 '12 at 22:01
  • 3
    Doesn't this reach the `return` statement before the handler gets called? – Levi Sep 27 '13 at 07:28