I am using [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) in my app. By using this my app is terminated in iOS 4.3 but it is working fine in iOS 5.0.
How to use this in iOS 4.3 can any one help me.
Here's a full implementation that works for me. Feel free to rename it and add as a category on NSURLConnection, or just add it as a local method in the class you're working in.
-(void)sendAsynchronousRequest:(NSURLRequest*)request queue:(NSOperationQueue*)queue completionHandler:(void(^)(NSURLResponse *response, NSData *data, NSError *error))handler
{
__block NSURLResponse *response = nil;
__block NSError *error = nil;
__block NSData *data = nil;
// Wrap up synchronous request within a block operation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
}];
// Set completion block
// EDIT: Set completion block, perform on main thread for safety
blockOperation.completionBlock = ^{
// Perform completion on main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
handler(response, data, error);
}];
};
// (or execute completion block on background thread)
// blockOperation.completionBlock = ^{ handler(response, data, error); };
// Execute operation
[queue addOperation:blockOperation];
}
EDIT
I had to modify the method because I was making UIKit calls in my completion block (e.g. updating labels etc). So it's actually a bit safer to call completion block on the main thread. (original version commented out)
The method you are trying to use is only available on iOS 5. For earlier OSes, consider using
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
and wrapping it into a new thread to achieve async behavior.
Both H2CO3 and Ken Thomases suggestions are right.
In addition, you could take a look at ios4-implementation-of-nsurlconnection-sendasynchronousrequestqueuecompletio.
If you use the main queue as the queue where the completion handler performs, you could use (as Tom suggested) the delegate pattern. To avoid duplicate code, you could use a wrapper on NSURLConnection delegates mechanism.
In the other case, if you want to maintain the async behaviour and you don't want to deal with sync call (as H2CO3 suggested, note that his suggestion is also valid) and the completion handler performs in a different queue, then I suggest you to wrap the async delegate pattern in a NSOperation class. This approach is quite difficult but you can find a good way of do this in Concurrent Operations Demystified (see both the posts).
Hope it helps.
Related
I'm calling a method that will enumerate through an array, create an NSURL, and call an NSURLSessionDataTask that returns JSON. The loop typically runs about 10 times but can vary depending on the day.
I need to wait for the for loop and all NSURLSessionDataTasks to complete before I can start processing the data.
I'm having a hard time figuring out when all the work is complete. Could anyone recommend any ways or logic to know when the entire method is complete (for loop and data tasks)?
-(void)findStationsByRoute{
for (NSString *stopID in self.allRoutes) {
NSString *urlString =[NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError = [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary * _stopDictionary in stopDirectionArray) {
NSArray *stop = [_stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
}];
[task resume];
}
}
There are a number of options. The fundamental issue is that these individual data tasks run asynchronously, so you need some way to keep track of these asynchronous tasks and establish some dependency on their completion.
There are several possible approaches:
The typical solution is to employ a dispatch group. Enter the group before you start the request with dispatch_group_enter, leave the group with dispatch_group_leave inside the completion handler, which is called asynchronously, and then, at the end of the loop, supply a dispatch_group_notify block that will be called asynchronously when all of the "enter" calls are offset by corresponding "leave" calls:
- (void)findStationsByRoute {
dispatch_group_t group = dispatch_group_create();
for (NSString *stopID in self.allRoutes) {
NSString *urlString = [NSString stringWithFormat:#"http://truetime.csta.com/developer/api/v1/stopsbyroute?route=%#", stopID];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
dispatch_group_enter(group); // enter group before making request
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
NSError *jsonError; // Note, do not initialize this with [[NSError alloc]init];
NSDictionary *stopLocationDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
NSArray *stopDirectionArray = [stopLocationDictionary objectForKey:#"direction"];
for (NSDictionary *stopDictionary in stopDirectionArray) {
NSArray *stop = [stopDictionary objectForKey:#"stop"];
[self.arrayOfStops addObject:stop];
}
}
dispatch_group_leave(group); // leave group from within the completion handler
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// do something when they're all done
});
}
A more sophisticated way to handle this is to wrap the NSSessionDataTask in a NSOperation subclass and you can then use dependencies between your data task operations and your final completion operation. You'll want to ensure your individual data task operations are "concurrent" ones (i.e. do not issue isFinished notification until the asynchronous data task is done). The benefit of this approach is that you can set maxConcurrentOperationCount to constrain how many requests will be started at any given time. Generally you want to constrain it to 3-4 requests at a time.
Note, this can also address timeout issues from which the dispatch group approach can suffer from. Dispatch groups don't constrain how many requests are submitted at any given time, whereas this is easily accomplished with NSOperation.
For more information, see the discussion about "concurrent operations" in the Operation Queue section of the Concurrency Programming Guide.
For an example of wrapping NSURLSessionTask requests in asynchronous NSOperation subclass, see a simple implementation the latter half NSURLSession with NSBlockOperation and queues. This question was addressing a different topic, but I include a NSOperation subclass example at the end.
If instead of data tasks you used upload/download tasks, you could then use a [NSURLSessionConfiguration backgroundSessionConfiguration] and URLSessionDidFinishEventsForBackgroundURLSession: of your NSURLSessionDelegate would then get called when all of the tasks are done and the app is brought back into the foreground. (A little annoyingly, though, this is only called if your app was not active when the downloads finished: I wish there was a rendition of this delegate method that was called even if the app was in the foreground when the downloads finished.)
While you asked about data tasks (which cannot be used with background sessions), using background session with upload/download tasks enjoys a significant advantage of background operation. If your process really takes 10 minutes (which seems extraordinary), refactoring this for background session might offer significant advantages.
I hate to even mention this, but for the sake a completeness, I should acknowledge that you could theoretically just by maintain an mutable array or dictionary of pending data tasks, and upon the completion of every data task, remove an item from that list, and, if it concludes it is the last task, then manually initiate the completion process.
Look at dispatch groups, for your example it will look roughly like this:
create group
for (url in urls)
enter group
start_async_task
when complete leave group
wait on group to finish or supply a block to be run when completed
[NSURLConnection sendAsynchronousRequest:mutURLRequest queue:opQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode ==200)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"MUITCheckinPostSucceeded" object:self userInfo:postDictionary];
}
}];
This is my NSURLConnection and I'm not sure how to check if it was successful. I tried a simple flag but that did not work because the boolean didn't retain the "YES" value outside of the NSURLConnection. This is a school assignment so don't post the correct code I'd just like to know the method I need to implement or how I can tackle this problem in a way I haven't tried yet. Thanks in advance.
Try something like this:
[NSURLConnection sendAsynchronousRequest: myURLRequest
queue: [NSOperationQueue mainQueue]
completionHandler: ^(NSURLResponse *urlResponse, NSData *responseData, NSError *requestError) {
// Check for Errors
if (requestError || !responseData) {
// jump back to the main thread to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
[myLabel setText: #"Something went wrong..."];
});
} else {
// jump back to the main thread to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
[myLabel setText: #"All going well..."];
});
}
}
];
You can update your class properties from the completion block. In this case, if flag was atomic, you can just update it. But if you're setting anything else (e.g. any object properties updated from the resulting data object), you might want to dispatch that back to the main queue to avoid synchronization issues:
self.flag = NO;
[NSURLConnection sendAsynchronousRequest:mutURLRequest queue:opQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSInteger statusCode = -1;
// to be safe, you should make sure `response` is `NSHTTPURLResponse`
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
statusCode = httpResponse.statusCode;
}
if (error)
{
// for diagnostic purposes only
NSLog(#"%s: sendAsynchronousRequest error: %#", __FUNCTION__, error);
}
if (error == nil && statusCode == 200)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.flag = YES;
// set any other class properties here
[[NSNotificationCenter defaultCenter] postNotificationName:#"MUITCheckinPostSucceeded" object:self userInfo:postDictionary];
}];
}
}];
I notice that you're posting a notification. If you have multiple view controllers or model objects listening for that notification, that's fine and a notification makes sense. But if this code was in the view controller and that controller is the only thing that cares about the results, you generally forego the notification and just initiate the update the UI right from the code that's dispatched back to the main queue in that completion block.
One final caveat. Any references to self (or ivars, which have an implicit reference to self) will maintain a strong reference to the object for the duration of the operation (i.e. it will retain it). For example, if you dismiss the view controller while the network operation is in progress, the view controller won't be released until after the network operation is done. That's often fine (as it's just for the duration of the connection ... it's not the dreaded strong reference cycle), especially for a school assignment. But if that's an issue, there are techniques to only use a weak reference to the view controller inside the completion block, thus preventing the retaining of the view controller for the duration of the network operation. But that's beyond the scope of your original question (esp since it leads to a bunch of other questions about whether you want to cancel the network operation or not, when you dismiss the view controller), so I'll leave it at here.
Currently using sendAsynchronous from NSURLConnection to make post and get requests, but can't get the status code in the response. Most post suggest the use of NSURLConnection and its delegate methods, which I understand are also Asynchronous.
I don't understand how the delegates send information back to the calling method (the method that eventually needs the data). The sendAsynchronous method has a call back that I am using right now.
I'm new to this, thank you for your help.
As you are using sendAsynchronousRequest:queue:completionHandle method, I will try to answer in that context:
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error) {
// This will get the NSURLResponse into NSHTTPURLResponse format
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
// This will Fetch the status code from NSHTTPURLResponse object
int responseStatusCode = [httpResponse statusCode];
//Just to make sure, it works or not
NSLog(#"Status Code :: %d", responseStatusCode);
}];
I have a wrapper class that makes this very easy and keeps your code clean:
https://github.com/ricardocontrerasrobles/EasyNetwork
I have the following code:
while ( /* Some condition that will not be met in this example */ ) {
if( shouldSendRequest ) {
[launchpad getRequestToken];
}
else {
// Next step
}
}
- (void)getRequestToken {
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[self requestForRequestTokenDidComplete:data withResponse:response withError:error];
}];
}
-(void)requestForRequestTokenDidComplete:(NSData *)data
withResponse:(NSURLResponse *)response withError:(NSError *)error {
// Deal with the returned token
}
The problem I have is that the completion handler in getRequestToken is never being called as long as getRequestToken is inside the while loop. As soon as I comment out the while loop, everything works.
What's happening here and is it possible to prevent it? I has planned to use the while loop to prevent the flow of execution moving on before this (and other) completion handlers had finished doing their thing.
The reason it's not working is because NSURLConnection works along with the runloop to perform the async request. Therefore if you stop the runloop by halting flow within the while statement you are preventing the request from completing.
You will need to artificially pump the runloop or use a background thread.
See:
Asynchronous request to the server from background thread
NSURLConnection sendAsynchronousRequest:queue:completionHandler: making multiple requests in a row?
GCD and async NSURLConnection
And lots of others...
All the server connections in my app goes the same way:
dispatch_async to a 2nd thread.
sendSynchronousRequest
fail/success blocks on main thread.
The following is not the full code, just the concept:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL *url = [NSURL URLWithString:#"someURL"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLResponse *response = nil;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error)
{
dispatch_async(dispatch_get_main_queue(), ^{ _failureBlock(error); });
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{ _successBlock(data); });
});
Well the app works great! I can use it for thousands of server connections, and it always works when I run a fresh load of the app.
The issue starts when the following occurs:
Send the app to BG.
Wait some time...
The app get killed by the iOS.
Open again, the iOS loads the whole app from scratch.
(see splash screen followed by the first screen, not the one I left to BG..)
In that scenario I get NSURLConnectionErrors!! All kind of them! Code 1003, Code 1009, Code 9... Just name it!
I get errors for all server connections that starts right when the app loads. Any other connections after that works fine! including the refresh of the ones that got failed!
It almost feel like i'm trying to reach my server too quick or too early, BUT I do check and pass a reachabillity test before intiating a connection.
Could really use some help here. Thanks in advance.
UPDATE: As suggested below, tried to use sendAsynchronousRequest - gave me the exact same behaviour. (err: 1001 this time).
OK got it! The appDelegate built the tabBar wich loaded all viewControllers wich sent some NSURLConnections... So for the iOS thought it's too long to wait for the responses and failed them all! (No idea way the first launch is ok and only after the app killed its not)
Anyway, I changed all server loads to perform with selector with 0.1 delay. That way the appDelegate could finish running and all is GOOD! :)
You should look at using this method and send the data Asynchronously
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
the NSOpertationQueue mainQueue uses the mainthread however allowing you to update UI also on the main thread i.e an activityindicator for example..