asoc_nerd
asoc_nerd

Reputation: 21

How to convert from synchronous to asynchronous NSURLConnection

I'm trying to update an old Mac OS program I wrote in ASOC (mostly Applescript, but some ObjC objects for things like web service access). I used a synchronous connection:

NSData *resultsData = [NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err];

The server credentials were embedded in the URL. This worked fine for me since the program really could not continue to do anything while the data was being fetched. A change to the server authentication method however has forced the need for changes to this application. I have tried all the usual workarounds with a NSURLCredential but that still does not work with this service.

So it looks like I will need to change to the asynchronous call:

[[NSURLConnection alloc] initWithRequest:request
                                delegate:self 
                        startImmediately:YES];

I have this working with the appropriate delegate methods, most importantly:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

Although I'd love to just use some form of delay loop to check for when the data has finished loading (essentially making it synchronous again), I have not found a way to do this that does not actually block the connection.

I am able to use a NSTimer to wait for the data before continuing:

set theJobListTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.05, me, "jobListTimerFired:", "", true)

    on jobListTimerFired_(theTimer)
      (jobListData as list) & count of jobListData
    if count of jobListData ≠ 0  then
        log "jobListTimerFired_ done"
        tell theTimer to invalidate()
        setUpJobList(jobListData)
    end if
end jobListTimerFired_

but this is clumsy and does not work while I'm in a modal dialog:

    set buttonReturned to current application's NSApp's runModalForWindow_(collectionWindow)

(I have a drop down in the dialog that needs to be updated with the results of the web service call). Right now, the delegate methods are blocked until the modal is dismissed.

Is there no simple way to emulate the synchronous call using the async methods?

Trying to use semaphore, I changed code to:

- (void) startConnection:(int)reqType :(NSMutableURLRequest *)request {

requestType = [NSNumber numberWithInt:reqType];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    // This could be any block that is run asynchronously
    void (^myBlock)(void) = ^(void) {

        self.connection = [[NSURLConnection alloc] initWithRequest:request
                                                          delegate:self 
                                                  startImmediately:YES];

    myBlock();

if (self.connection) {
        // create an object to hold the received data
        self.receivedData = [NSMutableData data];
        NSLog(@"connection started %@", requestType);
    }

   dispatch_time_t timeOut = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
    dispatch_semaphore_wait(semaphore, timeOut);
    dispatch_release(semaphore);
    semaphore = NULL;
}

then in the connection handler:

- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"connectionDidFinishLoading %@", requestType);

NSString *returnData = [[NSString alloc] initWithData:receivedData
                                             encoding:NSUTF8StringEncoding] ;
//      NSLog(@"connectionDidFinishLoading %@", returnData);

[self handleData:requestType :returnData];

[self terminate];

if(semaphore) {
    dispatch_semaphore_signal(semaphore);
}
}

However, the connectionDidFinishLoading handler (and for that matter the didReceiveResponse and didReceiveData handlers) do not get called until after the 10 second dispatch timeout. What am I missing here?

Upvotes: 2

Views: 285

Answers (2)

asoc_nerd
asoc_nerd

Reputation: 21

Found the answer here:

iOS, NSURLConnection: Delegate Callbacks on Different Thread?

I knew the connection was running on a different thread and tried various other while loops to wait for it to finish. But this was REALLY the magic line:

    while(!self->finished]){
    //This line below is the magic!
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}

Upvotes: 0

Steven Hepting
Steven Hepting

Reputation: 12504

You can use dispatch_semaphore_wait to make any asynchronous API into a synchronous one again.

Here's an example:

__block BOOL accessGranted = NO;

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

// This could be any block that is run asynchronously
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
    accessGranted = granted;
    if(semaphore) {
        dispatch_semaphore_signal(semaphore);
    }
});

// This will block until the semaphore has been signaled
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
semaphore = NULL;

return accessGranted;

Upvotes: 2

Related Questions