0

I set up a UITableView that is supposed to display some information once certain data is loaded from a website. In order for the UI to update and the data to finish loading I used the method:

performSelectorOnMainThread:@selector(getBooks) withObject:nil waitUntilDone:YES  

-(void)getBooks{

NSMutableString *theURL = [[NSMutableString alloc] initWithString:@"theURL"];

[theURL appendFormat:@"&UserID=%@", _appDel.userID];

NSLog(@"%@\n", theURL);
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:theURL]
                                            cachePolicy:NSURLRequestUseProtocolCachePolicy
                                        timeoutInterval:10.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
    receivedData = [NSMutableData data];
} else {
    // Inform the user that the connection failed.
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"An Error Occured" message:@"Couldn't make a connection to the server. Please make sure your internet connection is on." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles: nil];
    [alert show];
}

} However, when I run this the program continues executing although I set waitUntilDone to YES. I was under the impression that waitUntilDone would wait on the selector until it has finished executing. Can anyone explain what the possible issue could be? Thanks for any help.

Tim Stullich
  • 251
  • 6
  • 19
  • where are you calling this code from? are you also calling it from the main thread, or from a background thread? – Nate Jul 31 '12 at 08:47
  • I call it in the viewDidLoad method which should be from the main thread. – Tim Stullich Jul 31 '12 at 08:51
  • Tim, I thought I did this already, but I just wanted to give you a heads-up that I added to my answer **after** you accepted it. There are three ways to do this: 1) synchronously on the main thread (bad), 2) asynchronously on the main thread (sometimes ok ... and what my answer originally showed), and 3) on a true background thread (more complicated, but may be necessary for performance reasons). If you tried my initial code, and don't like the responsiveness, try what's in the update below. Thanks! – Nate Jul 31 '12 at 12:07
  • Awesome. I didn't have much of a performance issue, but that may be because I don't have much json to load yet. I'll definitely look into it this though. Thanks again for your great response. – Tim Stullich Jul 31 '12 at 19:49

1 Answers1

2

If you look at the Apple docs, they say:

wait

A Boolean that specifies whether the current thread blocks until after the specified selector is performed on the receiver on the main thread. Specify YES to block this thread; otherwise, specify NO to have this method return immediately. If the current thread is also the main thread, and you specify YES for this parameter, the message is delivered and processed immediately.

So, if you call this method from the main thread, then it will be executed immediately.

But, there's really no point in doing so. If you are on the main thread, and wish to execute getBooks on the main thread, then just do this:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self getBooks];
}

If you show us what you're actually doing inside getBooks, we might be able to help more. For example, if getBooks is making a remote HTTP request, to get online book data, then you don't want to use performSelectorOnMainThread: at all. You want to run that on a background thread, and then only call back the main thread to update your UI, once the network request has completed.


Update:

There's many ways to retrieve web content. If you are going to use the NSURLRequest directly, as you are, then you should make sure this class implements the NSURLConnectionDelegate protocol:

@interface MyViewController: UIViewController<NSURLConnectionDelegate> {

and then implement its methods per the Apple example

- (void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response
{
    // this method is called when the server has determined that it
    // has enough information to create the NSURLResponse

    // it can be called multiple times, for example in the case of a
    // redirect, so each time we reset the data.
    [receivedData setLength:0];
}

- (void) connection:(NSURLConnection *) connection didReceiveData:(NSData *)data
{
    // append the new data to the receivedData
    [receivedData appendData:data];
}

- (void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error
{
    // release the connection, and the data object
    [connection release];
    [receivedData release];

    // inform the user
    UIAlertView* netAlert = [[UIAlertView alloc] initWithTitle:@"" message:@"Oops!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [netAlert show];
    [netAlert release];
}

- (void) connectionDidFinishLoading:(NSURLConnection *) connection
{   
    if ([receivedData length] > 0) {
        // do something with the data, and then tell the UI to update
        [self performSelectorOnMainThread: @selector(updateUI) withObject: nil waitUntilDone: NO];
    }

    // release the connection, and the data object
    [connection release];
    [receivedData release];
    receivedData = nil;
}

-(void) updateUI {
    [tableView reloadData];
    someLabel.text = @"New Status";
    // whatever else needs updating
}

A note: the code above was written before ARC was introduced. If your project uses ARC, you'll need to remove all those lines with release calls. It is advisable, however, to make sure your receivedData is properly retained, since you'll be using it across multiple calls.

Another note: NSURLConnection can be used in multiple ways. It can be used synchronously, or asynchronously. And, you can always decide which thread to start it on. If you keep this:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self getBooks];
}

then the connection will be started on the main thread. But, it will still be an asynchronous operation. In other words, it won't block viewDidLoad until the network request finishes. However, if the user is going to be doing anything significant with the UI while the download happens (like scrolling), then you may find your UI becomes less responsive. If that's the case, you may either want to do this, or force the network operation onto a background thread. To do that, start it with:

-(void) viewDidLoad {
   [super viewDidLoad];
   [self performSelectorInBackground: @selector(getBooksInBackground) withObject: nil];
}

Then, the only problem with my above NSURLConnectionDelegate code is that the background thread will not live long enough to deliver the response via your delegate callbacks. To keep it alive,

-(void) getBooksInBackground {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    [self getBooks];

    CFRunLoopRun(); // Avoid thread exiting
    [pool release];
}

And then you must add this call to the end of both connectionDidFinishLoading and connection:didFailWithError to clean up this background run loop:

    CFRunLoopStop(CFRunLoopGetCurrent());

Again, old code, so use

    @autoreleasepool {
    }

for ARC.

Community
  • 1
  • 1
Nate
  • 31,017
  • 13
  • 83
  • 207
  • I see. I updated the post to include the getBooks method. How would I go about running the method in a background thread? – Tim Stullich Jul 31 '12 at 09:00
  • @Tim, thanks for the extra info. That helps. Give me a minute, and I'll post an update to do what you want. – Nate Jul 31 '12 at 09:02
  • Thanks, that helped. I already had these methods implemented in my class, but I just didn't have the NSURLDelegate implemented for the View class. – Tim Stullich Jul 31 '12 at 09:19
  • 1
    It seems to me that the main problem is that initWithRequest is asynchronous, for the OP's code to work as originally intended, the correct NSNURLConnection call would be + sendSynchronousRequest:returningResponse:error: Since this is already the winning answer, and the simple fix would leave an unresponsive app, but perhaps you could add a not to this effect. – Gordon Dove Jul 31 '12 at 09:24
  • @GordonDove, yeah, I'm typing something about that right now :) – Nate Jul 31 '12 at 09:30