I am running a background NSURLSession session and i am trying to figure out a way to get the JSON response out of one of the NSURLDownloadTaskDelegate callbacks. I have configured my session to accept JSON responses.
NSURLSessionConfiguration *backgroundSession = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.Att.Locker.BackgroundUpload"];
backgroundSession.HTTPAdditionalHeaders = #{ #"Accept":#"application/json"};
session = [NSURLSession sessionWithConfiguration:backgroundSession delegate:uploader delegateQueue:nil];
I can easily parse JSON response for NSURLSessionDownloadTasks using the following callback. It writes the JSON response onto the sandbox in the form of NSURL.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
//Reading the Json response from the sandbox using the NSURL parameter
}
My problem is if i encounter an error the callback above is not called, it seems to only get invoked in case of a successful download. Since i am using a background session i cannot use any of the NSURLSessionDataDelegate callbacks. I can only use the NSURLSessionDownloadTaskDelegate and NSURLSessionTaskDelegate and while i can get the task response using the following callback. I don't see the JSON in the response.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)downloadTask.response;
NSDictionary *httpResponse = [response allHeaderFields];
NSLog(#"Response Header Fields:%#",[httpResponse allKeys]);
}
NSURLConnection has a didReceiveData parameter which gives us an NSData object which we can use to get the JSON response. I don't see that in the delegate callbacks for NSURLSession except for NSURLDataTask but we cant use data tasks in the background so how are we supposed to get the JSON response out ? Any help is appreciated
EDIT:
I usually experience this issue while i am running the app in the background (mostly when it is kicked out memory and not just suspended). I have implemented the callbacks in the appDelegate and i am able to re associate with the session.I think didFinishDownloadingToURL is only invoked in case of successful completion of a task but when a task fails there is no guarantee its going to be called but on the other hand didCompleteWithError gets called every time there is a failure
Using a download task, you can get the data using the didFinishDownloadingToURL as you stated.
All NSURLSession tasks have this delegate as well. If you get in here and error is not nil, then walah you have an error. It does not need to complete to get in here. If it does get in here with an error, then the delegate didFinishDownloadingToURL will not be called.
If there is no error, and all of your data downloads, than both delegates will be called.
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(#"didCompleteWithError");
}
EDIT:
So something has to bet not setup correctly as there has to be a way to get the data.
Are you implementing application:handleEventsForBackgroundURLSession:completionHandler: in your AppDelegate which will hook your app back up to the completion handler to get the delegate calls?
I highly recommend watching the 2013 WWDC session #705, "Whats New in Foundation Networking". Background session talk begins at around 32 minutes, and the code demo begins around 37:50
Related
I have recently replaced NSURLConnection to NSURLSession in my code.
As I am using many synchronous url-requests and NSURLSession doesn't support one, I used semaphores to make NSURLSessionDataTask synchronous.
I referred this link: https://forums.developer.apple.com/thread/11519
I have a singleton 'Network Manger' with NSURLSession as a member variable. NSURLSession is instantiated only once and tasks are added to it.
But now synchronous request cause performance issues in my app. There are lags and app hangs when synchronous request is sent.
Her is the call to synchronous request:
NSURLResponse *urlResponse;
NSError *error;
id serverResponse=[[MyNetworkManager sharedInstance] synchronousDataTaskWithRequest:request
returningResponse:&urlResponse
error:&error];
Everything works perfectly if I do not make NetworkManager as singleton and instantiate it everytime.
MyNetworkManager *manger=[[MyNetworkManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] responseSerializer:nil];
NSURLResponse *urlResponse;
NSError *error;
id *serverResponse=[manger synchronousDataTaskWithRequest:request returningResponse:&urlResponse error:&error];
Notes:
1.Exact Steps: There are total 3 (2 async and 1 sync ) NSURLSessionDataTasks. Second async task is called in completion handler of first task. Third synchronous task is called from completion handler of second. This third task is blocked forever and code doesn't proceed.
2.Everything is happening on background thread.
Why does synchronous data task work only if it is added to a new NSURLSession? Why it doesn't work if added to NSURLSession which has already executed two data tasks?
I was facing the similar issue when I used to synchronus implementation using Semaphore and here is the cause of that issue :
I think that your an HTTP request is causing a redirection, and in the willPerformHTTPRedirection method, you haven't called the completionHandler for the new request. Try implementing the method as :
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSLog(#"willPerformRedirection");
completionHandler(request);
}
When trying to create a NSURLSessionUploadTask using AFNetworkings AFURLSessionManager in a share extension I keep getting an error. The delegate for the session keeps getting called for
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
With the error
Error Domain=NSURLErrorDomain Code=-996 "Could not communicate with background transfer service" UserInfo=0x60800007a6c0 {NSLocalizedDescription=Could not communicate with background transfer service}
This happens if the app has a session and then the share extension tries to make a session. To init the session I do the following in both the app and the share extension.
NSURLSessionConfiguration* config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSHARE_UPLOAD_SESSION];
config.sharedContainerIdentifier = kAPP_GROUP;
self.sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
self.sessionManager.attemptsToRecreateUploadTasksForBackgroundSessions = YES;
You must use a different "Identifier" (kSHARE_UPLOAD_SESSION) for the main app & the extension. You apparently cannot have 2 NSURLSession with the same identifier running at the same time. You should keep the same group though (kAPP_GROUP)
I want to check if a web ressource is available without download any data. For exemple, if I do a NSURLConnection on a webPage, I can get the status code:
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];//200 is ok
}
But all page datas are downloaded.
How can I get this code (or equivalent) without download this page ?
The status code comes along with the server answer, once you get the status code you will have the body as well.
Another option is maybe to check if the resource is reachable using the Reachability classes.
Check this example, it may help.
Update: borrrden pointed in the right direction here (use HEAD method instead of GET) check the comment on your anwser
I have a web server that is streaming JSON results back asynchronously to an iOS client. The client is connecting using NSURLConnection and I access the data from the method:
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData)
Data is currently coming back in 1024 byte chunks. However, I'm not sure how to tell if when I receive data if the message was complete other than appending all the data I receive to a string and try to parse it to JSON each time. This method seems quite error prone - is there a better way to handle this? Something that would mark in the headers or something when a full response has been sent?
You have two ways
first & better way is implement connectionDidFinishLoading: NSURLConnectionDataDelegate delegate which will trigger when a connection has finished loading successfully.
Second way is handling it manually as follows.
You can do the following things in Web-server side,
Step1: Send the below informations first before starting to send the original data.
a.Number of Chunks.[totalSize/1024] (mandatory).
b.TotalSize(not mandatory).
You can do the following things in Client side side,
Step1: Store the above informations.
Step2: Write the below code
#property (nonatomic,assign) int chunkNumber;
#property (nonatomic,strong) NSData *receivedData;
Self.chunkNumber = 1;
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData)myata{
if(self.chunkNumber != Number of Chunks)
{
if(!self.receivedData)
{
//allocate and initialize self.receivedData
}
[self.receivedData appendData:myData];
}
else
{
//completed . do whatever with self.receivedData.
//if you want to validate, just check the self.receivedData size with TotalSize
self.chunkNumber = 1;
}
}
In the NSURLConnectionDataDelegate, there is a method connectionDidFinishLoading: that should be called when the server is done sending. You can also retrieve the expected length in didReceiveResponse but that is not reliable and required server side support.
What does a web service need to do to cause NSURLConnection's delegate to receive the connection:didFailWithError: message?
For example:
iOS app passes a token to the web service, web service looks up the token, and then the web service needs to respond with an error saying "invalid token" or something of the like.
Currently, the data is received, and in "connectionDidFinishLoading:" is parsed for error messages. This means I'm error checking in two places, which I am trying to avoid.
I have both the iOS app and web service completely under my control.
In my experience (the three most dangerous words in programming), -connection:didFailWithError: is only called if the HTTP exchange failed. This is usually a network error or maybe an authentication error (I don't use authentication). If the HTTP message succeeds, no matter the response code, -connectionDidFinishLoading: is called.
My solution: call -connection:didFailWithError: when I detected an error. That way all my error handling code is in one place.
At the top of my -connectionDidFinishLoading:, I have:
NSError *error;
NSDictionary *result = [self parseResultWithData:self.connectionData error:&error];
if (!result) {
[self connection:connection didFailWithError:error];
return;
}
There are many conditions on which the delegate connection:didFailWithError: of NSUrlConnection may invoke.Here's a list of those errors or constants.I think an alertview would be better to show http errors in connection:didFailWithError:.
-(void) connection:(NSURLConnection *)connection didFailWithError: (NSError *)error
{
UIAlertView *errorAlert= [[UIAlertView alloc] initWithTitle: [error localizedDescription] message: [error localizedFailureReason] delegate:nil cancelButtonTitle:#"Done" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
NSLog (#"Connection Failed");
}
While not directly related to your question, I would encourage you to move to a more high level library. I can heartily recommend AFNetworking, it is production ready and I have used it in many projects. This will allow you to inspect the response code of each request in the failure block. This project also abstracts away a lot of the low level handling that you would otherwise be required to write for network communication; I'm speaking here about parsing and creating XML / JSON strings to communicate with a service.
To give you a more focused answer to your question, I would call the cancel method of your NSURLConnection once you have noticed an error in connectionDidFinishLoading:. This will automatically cancel the request and call the failure method of the delegate object.
The documentation for NSURLConnection is pretty dry, and the failure method of the delegate does not specifically document the failure cases. You may be able to find more information in the URL Loading System Programming Guide.
I couldn't see the forrest for the trees.
I needed to step back from connection:didFailWithError: and look at a different delegate method connection:didReceiveResponse:!!
With the web service fully under my control, the endpoint could respond with a 500 status code, which gets picked up in connection:didReceiveResponse, and pass along some JSON further explaining the situation, which gets picked up and processed in connection:didReceiveData:.
The NSURLConnection delegate hangs onto a couple more bits of state throughout the process, but it has the best code smell I've found so far.
Jeffery's answer was by far most correct: the connection:didFailWithError: callback is only in relation to the network failing, any response from the web service means the connection didn't fail!