Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make program wait for asynchronous NSURLConnection to finish before proceeding with next command?

How can I make my program wait for an asynchronous NSURLConnection to finish before going to the next line of code?

SetDelegate *sjd= [SetDelegate alloc];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:post delegate:sjd];
[connection start];

This is how I start the connection and I handle the data received in the delegate but I want to wait for the connection to end before proceeding mainly because this is in a for loop and it has to run for each element in my database.

I need to put data from the phones database to a remote database and after the data was successfully put in the data in the phones database is deleted. I am going through each element in the phone's database and start a connection that's why I don't see how the next stuff can be done from the loop. I'm a beginner when it comes to objective-c programming so I'm not sure if this is the right way or not to do it

Making the call synchronous is not an option because it blocks the program and i have a progress bar that should show.

like image 243
Pillblast Avatar asked Dec 08 '11 07:12

Pillblast


2 Answers

Your question is a bit odd. You have impossibly constrained the issue. You cannot have a line of code "wait" for a process to finish w/o it blocking something, in this case whatever thread the loop is running in.

You can use a synchronous call if you wanted to, it doesn't block your app, it only blocks the thread it is executed on. In your example, you have a loop that is continually getting remote data and you want your UI to reflect that until it is done. But you don't want your UI blocked. That means, this thread with your loop already MUST be on a background thread so you can feel free to do a synchronous call in the loop w/o blocking your UI thread. If the loop is on the UI thread you need to change this to do what you want.

You could also do this using an asynchronous connection. In that case, your operation may actual complete faster b/c you can have multiple requests in progress at the same time. If you do it that way, your loop can remain on the UI thread and you just need to track all of the connections so that when they are finished you can communicate that status to the relevant controllers. You'll need iVars to track the loading state and use either a protocol or NSNotification to communicate when loading is done.

EDIT: ADDED EXAMPLE OF SYNCHRONOUS CALL ON BACKGROUND THREAD

If you want the loop to finish completely only when all requests are finishes and not block your UI thread here's a simple example:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // post an NSNotification that loading has started
    for (x = 0; x < numberOfRequests; x++) {
        // create the NSURLRequest for this loop iteration
        NSURLResponse *response = nil;
        NSError *error = nil;
        NSData *data = [NSURLConnection sendSynchronousRequest:request
                                             returningResponse:&response
                                                         error:&error];
        // do something with the data, response, error, etc
    }
    // post an NSNotification that loading is finished
});

Whatever objects need to show loading status should observe and handle the notifications you post here. The loop will churn through and make all your requests synchronously on a background thread and your UI thread will be unblocked and responsive. This isn't the only way to do this, and in fact, I would do it using async connections myself, but this is a simple example of how to get what you want.

like image 183
XJones Avatar answered Sep 19 '22 00:09

XJones


If you just want to know when it's complete and don't really care about any data, simply use the NSNotificationCenter to post a notification and have your view subscribe to it.

Delegate - Post Notification upon completion

-(void) connectionDidFinishLoading:(NSURLConnection*)connection {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NSURLConnectionDidFinish" object:nil];
}

View - Add observer and run some code when observed

-(void) viewDidLoad {

[[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(yourCleanupMethod)
                                               name:@"NSURLConnectionDidFinish"
                                              object:nil];
}

-(void) yourCleanupMethod {
    // finish up

    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Now, if you need to pass a simple object back as data you can try loading up the object parameter in your notification like this:

[[NSNotificationCenter defaultCenter] postNotificationName:@"NSURLConnectionDidFinish" object:yourDataObject];

Then change your view and cleanup signature like this:

-(void) viewDidLoad {

// Notice the addition to yourCleanupMethod
[[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(yourCleanupMethod:)
                                               name:@"NSURLConnectionDidFinish"
                                              object:nil];
}

-(void) yourCleanupMethod:(NSNotification *)notif {
    // finish up
    id yourDataObject = [notif object];

    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

Now I found myself needing something a little more than this so I ended up creating a singleton to handle all of my requests. Since all of your delegate methods in NSURLConnectionDelegate give you and instance of the NSURLConnection for the specific connection, you can simply store a mutable data object in a dictionary and look it up each time by the connection. From there I have a method signature that takes and object and selector in that I associate with the connection so after everything has wrapped up, I can pass that mutable data object to the requestor by performing the selector on that object.

I won't include all of that code here but maybe that will help get you thinking about what is possible. I found that I had a lot of code tied up in making web service calls so wrapping everything up in a singleton gave me a nice clean way of getting data. Hope this helps!

like image 45
jerrylroberts Avatar answered Sep 20 '22 00:09

jerrylroberts