Reputation: 5240
I'm creating an application that fetches a set of results from a database - I use MBProgressHUD to show the progress of the query with an animation. The method I use calls the animation while executing a method in another thread and once it's done, it hides the animation. My question is, after calling:
[HUD showWhileExecuting:@selector(getResults) onTarget:self withObject:nil animated:YES];
I would like to, if there are no results, display an alert stating this, and if there are, load the next view. So far, I have this code:
[HUD showWhileExecuting:@selector(getResults) onTarget:self withObject:nil animated:YES];
if(self.thereAreEvents) {
[self performSegueWithIdentifier:@"searchResults" sender:self];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No results" message:@"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
self.thereAreEvents
gets set at the end of the getResults
method. However, since that method gets called in another thread this line of execution continues and shows the alert, even though there are events in the database.
So, from here, I have two questions: What is the easiest way to implement a wait-signal mechanism in iOS and what is the most efficient way to implement this sort of mechanism in iOS?
Thanks!
Upvotes: 6
Views: 15247
Reputation: 3684
you can use dispatch_time_t as..
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
Upvotes: 0
Reputation: 100652
You could also consider an NSConditionLock
.
So it'd be something like this on thread 1:
[conditionLock lockWhenCondition:kConditionOkayToProceed];
[conditionLock unlockWithCondition:kConditionGettingResults];
[HUD show...]
[conditionLock lockWhenCondition:kConditionResultsFetched];
[conditionLock unlockWithCondition:kConditionOkayToProceed];
And in the HUD:
- (void)show...
{
[conditionLock lockWhenCondition:kConditionGettingResults];
// stuff here
[conditionLock unlockWithCondition:kConditionResultsFetched];
}
Though a much better solution would be to pass a block or a target/selector to the HUD that it is to perform when results are fetched.
EDIT: so you'd end up with code like:
[HUD showWhileExecuting:@selector(getResults)
onTarget:self
withObject:nil
animated:YES
performWhenFinished:
^{
if(self.thereAreEvents) {
[self performSegueWithIdentifier:@"searchResults" sender:self];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No results" message:@"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
}];
And in the HUD:
- (void)showWhile... performWhenFinished:(dispatch_block_t)block
{
// all the other stuff you were going to do here, then
// eventually...
// if no guarantees, maybe just:
block();
// otherwise, if promised to dispatch to the main queue:
dispatch_async(dispatch_get_main_queue(), block);
}
With HUD having the extra intelligence to take a dispatch_block_t
as a final argument and to call it when results are in (whether guaranteeing dispatch back to the main thread or otherwise).
Upvotes: 2
Reputation: 104115
You can use a busy wait loop for a quick and dirty solution:
__block BOOL finished = NO;
dispatch_async(/* global queue */, ^{
// …
finished = YES;
});
while (!finished) /* waiting */;
In “real” code it’s better to use a semaphore:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(/* global queue */, ^{
// …
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(sempahore);
This is better than the busy loop because the blocked thread does not eat CPU time.
The best solution is to keep from blocking and redesign your code to work asynchronously. In your case, you should display a spinner and start downloading data. When the data is finished downloading, you should receive an asynchronous callback (either through a block or a target/action callback) and display the results or show the error alert. Blocking with a busy loop or a semaphore is a poor man’s solution in this case.
Upvotes: 12
Reputation: 2368
This is your way: Concurrency Programming Guide
Also: Synchronization
More sharp: Using Locks. I think the last one could give the best help.
Another simple approach, it's bad but works
NSAssert(![NSThread isMainThread], @"Do not run on MAIN thread");
while (yourCondition) { [NSThread sleepForTimeInterval:0.2]; }
Upvotes: 1
Reputation: 2492
Not sure if this is the best way to do it but I'd try using a while loop to observe some boolean with something like [NSThread sleepForTimeInterval:1]; inside the loop.
Maybe set a timeout as well.
Upvotes: 0