9

Is it generally a good idea to call -[NSRunLoop runUntilDate:]? It seems to work without any issues, but it makes me nervous to tell the run loop to run from within the run loop.

More info:

I have a project right now that is fetching data from a REST service. One critical piece of information that needs to be obtained is the range of dates with valid data. It's a very small bit of data that only needs to be gotten once, so I decided that the best way to handle it is to have the property download the data if the local variable is nil. I'm using ASIHTTPRequest and an ASINetworkQueue, so everything is asynchronous by default, and in order for this to work, this property can't return until the data has been downloaded and processed. Here's an outline of my code, the names of variables have been changed to protect the innocent:

__block BOOL isWorking = YES;
__block ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:/*actual URL*/] autorelease];
[request setCompletionBlock:^{
    // set local variable
    isWorking = NO;
}];
[request setFailedBlock:^{
    // show alert to user
    isWorking = NO;
}];
[queue addOperation:request];

while (isWorking) {
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

Again, this seems to be working fine. Are there any potential issues with using this approach?

Jose Ibanez
  • 3,325
  • 3
  • 28
  • 33

2 Answers2

4

Isn’t it better to display some kind of a spinner and tear it down in response to the async completion events from the networking code? Like:

[self displayLoadingSpinner];
[request setCompletionBlock:^{
    [self handleSuccess];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self hideLoadingSpinner];
    }];
}];
[request setFailedBlock:^{
    [self handleFailure];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self hideLoadingSpinner];
    }];
}];
[queue addOperation:request];

I would consider this better than monkeying with the run loop. But it could be that you already know this and just want to know what exact drawbacks are there in the runloop solution?


If you want to block until the value is ready, you can use a semaphore:

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[request setCompletionBlock:^{
    dispatch_semaphore_signal(sem);
}];
[queue addOperation:request];

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_release(sem);
zoul
  • 102,279
  • 44
  • 260
  • 354
  • This is the approach I would take if a delegate/callback method approach were possible. In this case, I need the value to be set before I return from the property or else the application will continue with bad data. I could refactor the code so that design pattern would work. This question is really about finding out if there could be any unexpected issues with my approach that I'm just not seeing yet. – Jose Ibanez Feb 07 '11 at 18:29
  • Wouldn't the semaphore approach block the main thread? Wouldn't that prevent the blocks from executing? – Jose Ibanez Feb 07 '11 at 20:13
  • It depends on how the HTTP library you use is implemented. Yes, the main thread will block, so that if the library uses the main thread, you can't use the semaphore. – zoul Feb 07 '11 at 20:23
  • Yeah, it looks like ASIHTTPRequest calls all of the blocks from the main thread. – Jose Ibanez Feb 07 '11 at 21:10
  • In that case my answer is getting more and more useless :-) – zoul Feb 07 '11 at 21:13
3

You have to make sure not to do this from any method that could be called by the run loop you are invoking, unless the overlapping call tree is completely re-entrant.

The Cocoa Touch UI code is not documented as re-entrant (in fact, there are warnings/hints from Apple DTS that it is not), so if your fetch data handler can in any way be called by a UI method (or other non-reentrant code that could be called in the UI run loop), calling the UI run loop from inside it is not recommended.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • So, in this case, it sounds like this approach is safe since there's nothing else that could call this property while it's busy downloading the data. But if there were a situation where the user could hit a button while it was busy and call that property again, then it would blow up? Good to know. – Jose Ibanez Feb 07 '11 at 18:32
  • Is this property used directly or indirectly by any UI method or delegate? If so, it's still a big problem. – hotpaw2 Feb 07 '11 at 19:07
  • I'm certain it will only hit this code once when the app initially loads before the UI is even presented to the user. After that, the data will be there and it'll use the local variable. If it can't download the data for some reason, it displays a modal alert to the user and they can't get into the app until it downloads successfully. – Jose Ibanez Feb 07 '11 at 20:10
  • What do you mean by reentrant? – airpaulg Jul 23 '13 at 15:22