1

Im developing ios application which is getting data from the web server. I want everything else to wait, until one of the handlers of this class is called and completed. I know it is possible by using dispatch/threads, but i just can't figure out how.

-(void)callWebService:(NSString*)URL:(NSString*)SOAP{
     NSURL *url = [NSURL URLWithString:URL];
     NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];

     [req setHTTPMethod:@"POST"];
     [req setHTTPBody:[SOAP dataUsingEncoding:NSUTF8StringEncoding]];


     NSURLConnection *con = [[NSURLConnection alloc] initWithRequest:req delegate:self];
     if(con){
         [con start];
     }
}

and at the end of this method continues code outside this class. but i want to wait until this handler is called (and completed):

-(void)connection:(NSURLConnection *)c didReceiveData:(NSData *)data{
    NSString *res = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",res);
    Ukol_Parser *parser = [Ukol_Parser alloc];
    [parser parseUkol:res];
}

because the parser here puts data into sqlite db and outside this class data are being read. But the "outside" code is being executed faster than i get response and handler is called....

Rob
  • 415,655
  • 72
  • 787
  • 1,044
Casper522
  • 95
  • 1
  • 12
  • http://stackoverflow.com/questions/9409211/nsurlconnection-sendasynchronousrequestqueuecompletionhandler-making-multiple – Tomasz Dubik Nov 10 '12 at 18:31
  • Sorry, totally missed that. Thanks ! – Casper522 Nov 10 '12 at 18:37
  • except this doesnt really work since i need to handle certificate and trust challenges i still get 'kicked' outside the object meanwhile... – Casper522 Nov 10 '12 at 19:08
  • SO to this in asynchronously way and implement NSURLConnectionDelegate calls. – Tomasz Dubik Nov 12 '12 at 14:19
  • BTW, you shouldn't `start` a `NSURLConnection` (unless you called `initWithRequest` with the `startImmediately` set to `NO`) because it will automatically be started for you. Also, given that it may take multiple calls to `didReceiveData` before you receive all of your data, you want `didReceiveData` to only append data to a `NSMutableData`, and defer the actual parsing until `connectionDidFinishLoading`. – Rob Aug 25 '13 at 04:59

5 Answers5

2

If you want "everything else to wait", then it sounds like what you really want to do are synchronous requests.

Check out [NSURLConnection sendSynchronousRequest:returningResponse:error:]

However, make sure to do this thing on a background thread because if you do it on the main thread, your UI will block and your app will look unresponsive to user touches or anything else.

Neeku
  • 3,646
  • 8
  • 33
  • 43
Michael Dautermann
  • 88,797
  • 17
  • 166
  • 215
  • 1
    +1 While doing `sendSynchronousRequest` will make everything wait if you do it from the main queue, that's obviously a train wreck of a UX (frozen app, watchdog may kill app, etc.). And doing a synchronous request from a background queue will suffer from precisely the same problem that his current implementation does. I wonder if Casper522 fully absorbed the import of your caveat about only doing this on a background queue. – Rob Aug 25 '13 at 04:45
  • According the OP's question and phrasing, it is very clear that a "synchronous" approach is NOT what he/she is searching for. Otherwise, why would the OP mention "dispatch/threads"? Furthermore giving an answer which is considered bad practice isn't very helpful, too. – CouchDeveloper Aug 25 '13 at 06:47
  • Hello @CouchDeveloper ; I put the caveat about "make sure to do this thing on a background thread" to warn him not to block his main (UI) thread. If the OP does his coding right, he can do his synchronous processing on a background thread and send a message (either delegate or NSNotification or whatever) to the main thread saying "I have results (or an error)". I'm really not understanding what you and Rob's squawking here is all about. And why is this question even active again after 8 months if Casper was apparently able to figure out what to do via my answer? – Michael Dautermann Aug 25 '13 at 19:24
  • 1
    LOL yes, this is an old question - I didn't noticed this. Perhaps I just noticed @Rob's new comment and added mine ;) Not sure if the problem could actually be solved with `sendSynchronousRequest:` since the OP apparently needed to implement delegate methods to do some certificate stuff. The reason for my comment is, I do consider wrapping asynchronous code into a synchronous method as "bad practice". I think we can solve such problems much better. Learning how to approach this sort of problems correctly will often require a much more elaborated answer, though. – CouchDeveloper Aug 25 '13 at 20:24
1

I'm nervous that you accepted the answer regarding sendSynchronousRequest from the background queue because, from a practical perspective, this is no different than your didReceiveData-based implementation. Specifically, if you do perform synchronous request from a background queue, it will not make "everything else wait". But if you neglect to do this synchronous request from the background queue (i.e. if you do it from the main thread), you end up with a horrible UX (the app is frozen and the user is wondering whether the app has crashed), and worse, your app could be killed by the iOS "watchdog process" if it takes too long.

With all deference to the various answers, sending a synchronous request on a background queue is indistinguishable from the existing NSURLConnectionDataDelegate-based approach. What you really need to do is accept the fact that the rest of the app will not freeze, and therefore simply update the UI to let the user know what's happening, namely that (a) provide some visual cue that the app is not dead; and (b) prevent the user from interacting with your existing UI.

For example, before issuing your network request, add a view that will cover/dim the rest of your UI, prevent user interaction with the existing UI, and add a spinner to let the user know that the app is busy doing something. So, define a class property for a UIView that will dim the rest of the UI:

@property (nonatomic, strong) UIView *dimView;

And then, before the network request, update the UI:

// add a view that dims the rest of the UI, so the user has some visual cue that they can't use the UI yet
// by covering the whole UI, you're effectively locking the user out of using the app
// until the network request is done

self.dimView = [[UIView alloc] initWithFrame:self.view.bounds];
self.dimView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
self.dimView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.dimView];

// add a spinner that shows the user that something is really happening

UIActivityIndicatorView *indicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicatorView.center = self.dimView.center;
indicatorView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
[indicatorView startAnimating];
[self.dimView addSubview:indicatorView];

[self callWebService:URL withSOAP:SOAP];

And then, in the connectionDidFinishLoading method (or if you used sendSynchronousRequest from a background queue, in the latter portion of code to dispatched to that background queue), after you finish parsing your data, you want to:

[self.dimView removeFromSuperview];
self.dimView = nil;

// now do what ever to need do to update your UI, e.g.:
//
// [self.tableView reloadData] 

But the key is that you absolutely do not want to issue a synchronous network request from the main queue. You should (a) do your network request asynchronously (either synchronously on a background queue, or as you originally implemented) so that the iOS watchdog process doesn't kill your app; (b) let the user know the app is making some network request and hasn't frozen on them (e.g. a UIActivityIndicatorView; and (c) when the request is done, remove these "the app is doing network request" UI elements and refresh the rest of the UI now that your network request is done.

Finally, when testing your app, make sure you test it in real-world networking situations. I'd suggest you install the Hardware IO tools (available in Xcode menu, under "Open Developer Tools" - "More Developer Tools") and check out the Network Link Conditioner. This lets you simulate real-world network situations (e.g. a bad 3G or Edge network condition) on the iOS simulator. We get lulled into a false sense of performance when we test our apps in typical development environments with ideal network connectivity. Devices "in the wild" suffer a wide range of degraded network situations, and it's good to test your app in a similar, suboptimal network situation.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

kind of a wild solution, but this actually worked https://gist.github.com/62940 :D

Casper522
  • 95
  • 1
  • 12
  • I don't think that's a good way to do it. You should learn the correct way to structure your program to handle long running or asynchronous events. – rdelmar Nov 10 '12 at 20:53
0

use synchronous calls . but It'd change the design of your class because a synchronous call will block and leave the app hanging

Daij-Djan
  • 49,552
  • 17
  • 113
  • 135
0

Post a notification from your didReceiveData: method, and have your other class observe that notification (or you could use a delegate setup, if it's easy to get a reference to this class from the other so you can set your other class as the delegate of this one). In the notification's selector method, start executing the rest of your code.

rdelmar
  • 103,982
  • 12
  • 207
  • 218