1

Hi there: I have been writing an iOS program which uses many http queries to the backend rails server, and hence there are tons of codes like below. In this case, it is updating a UITableView:

//making requests before this...
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
    NSLog(@"Request sent!");

    NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
    NSLog(@"Response code: %d", [httpResponse statusCode]);
    if ([data length] > 0 && error == nil){
        NSLog(@"%lu bytes of data was returned.", (unsigned long)[data length]); }
    else if ([data length] == 0 &&
             error == nil){
        NSLog(@"No data was returned.");
    }
    else if (error != nil){
        NSLog(@"Error happened = %@", error); }

    id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (jsonObject != nil && error == nil){
        NSLog(@"Successfully deserialized...");
        if ([jsonObject isKindOfClass:[NSDictionary class]]){
            NSDictionary *deserializedDictionary = (NSDictionary *)jsonObject;
            NSLog(@"Dersialized JSON Dictionary = %@", deserializedDictionary);
            [listOfItems addObject:deserializedDictionary];
        }
        else if ([jsonObject isKindOfClass:[NSArray class]]){
            NSArray *deserializedArray = (NSArray *)jsonObject; 
            NSLog(@"Dersialized JSON Array = %@", deserializedArray);
            [listOfItems addObjectsFromArray:deserializedArray];
        }
        else {
            /* Some other object was returned. We don't know how to deal
             with this situation as the deserializer only returns dictionaries
             or arrays */ }
    }
    else if (error != nil){
        NSLog(@"An error happened while deserializing the JSON data., Domain: %@, Code: %d", [error domain], [error code]); 
    }
    [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];
}];
//the place where never runs
NSLog(@"End of function.");

Here is the problem: the last line gets executed usually before the code block. How do I ensure that the code after block actually runs after the block?

I am aware that the block uses some other threads, which is why I use performSelectorOnMainThread function instead of a direct call of [self.tableView reloadData]. But if I want to do something else afterward, how am I supposed to do?

Also, can anyone show some better ways to do this? I am trying to figure out the best way to make massive calls to the backend. There are several ways to make asynchronous requests, including this block way and another old-fashioned way invoking delegate classes. In the progress to refactor the codes, I also tried to create my own delegate class and let other classes invoke that, but it is difficult to identify the correct behaviour of callback functions for which connection's data it returns, especially for classes that use multiple functions to call different requests. And I don't want to use synchronous calls.

Thanks very much for any answers. Also welcome to point out any bugs in the code.

ThomasCle
  • 6,792
  • 7
  • 41
  • 81
wlicpsc
  • 389
  • 1
  • 6
  • 13
  • are you seeing "End of function" print before it reaches the end of the asynchronous block? i.e before "Request sent!" – Jesse Gumpo Jul 21 '12 at 08:39
  • Very Sorry, I saw that immediately after I posted this question. So I have edited it and keep asking other questions here. Thanks for notice. – wlicpsc Jul 21 '12 at 08:42
  • Session 211 from this year's WWDC has lots of information about implementing this sort of structure using NSOperationQueue, it's a great talk. – jrturton Jul 21 '12 at 11:29

1 Answers1

2

You can using dispatch group

Sample code:

- (void)doSomethingAndWait {
// synchronous method
// called in main thread is not good idea.
NSAssert(! [NSThread isMainThread], @"this method can't run in main thread.");

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);

//making requests before this...
NSOperationQueue* queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse* response, NSData* data, NSError* error)
{
    // your work here.


    dispatch_group_leave(group);
}];

// wait for block finished
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

//will call until block is finished.
NSLog(@"End of function.");
}

And to call that method, you need avoid call it in main thread.

you should call it like this

dispatch_queue_t queue = dispatch_queue_create("com.COMPANYNAME.APPNAME.TASKNAME", NULL);
    dispatch_async(queue, ^{
        [self doSomethingAndWait];
    });
sycx
  • 891
  • 6
  • 8
  • Thanks. Seems pretty useful. I tried the other way, with sync requests in the async block to make refactoring easier. The Cookbook (cat book) also gives an example like that. For the 1st way, it's a bit hard to wrap it in my own class to invoke because of that handler, since I want the response data to be returned universally. Are these the only way to make it? – wlicpsc Jul 21 '12 at 10:59