Make iOS blocks execute synchronously
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 requestAccessToAc开发者_如何学GocountsWithType:accountType withCompletionHandler:^(BOOL granted, NSError *error) {
data = [NSData dataWithData:responseData];
}];
return data;
}
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.
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
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
}];
}
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
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.
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;
}
精彩评论