How to get server response data in NSURLSession without completion block
A couple of thoughts:
First, instantiate your session with a delegate
, because background sessions must have a delegate:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kSessionIdentifier];self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
Second, instantiate your NSURLSessionUploadTask
without a completion handler, because tasks added to a background session cannot use completion blocks. Also note, I'm using a file URL rather than a NSData
:
NSURLSessionTask *task = [self.session uploadTaskWithRequest:request fromFile:fileURL];[task resume];
Third, implement the relevant delegate methods. At a minimum, that might look like:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { NSMutableData *responseData = self.responsesData[@(dataTask.taskIdentifier)]; if (!responseData) { responseData = [NSMutableData dataWithData:data]; self.responsesData[@(dataTask.taskIdentifier)] = responseData; } else { [responseData appendData:data]; }}- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (error) { NSLog(@"%@ failed: %@", task.originalRequest.URL, error); } NSMutableData *responseData = self.responsesData[@(task.taskIdentifier)]; if (responseData) { // my response is JSON; I don't know what yours is, though this handles both NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil]; if (response) { NSLog(@"response = %@", response); } else { NSLog(@"responseData = %@", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]); } [self.responsesData removeObjectForKey:@(task.taskIdentifier)]; } else { NSLog(@"responseData is nil"); }}
Note, the above is taking advantage of a previously instantiated NSMutableDictionary
called responsesData
(because, much to my chagrin, these "task" delegate methods are done at the "session" level).
Finally, you want to make sure to define a property to store the completionHandler
provided by handleEventsForBackgroundURLSession
:
@property (nonatomic, copy) void (^backgroundSessionCompletionHandler)(void);
And obviously, have your app delegate respond to handleEventsForBackgroundURLSession
, saving the completionHandler
, which will be used below in the URLSessionDidFinishEventsForBackgroundURLSession
method.
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { // This instantiates the `NSURLSession` and saves the completionHandler. // I happen to be doing this in my session manager, but you can do this any // way you want. [SessionManager sharedManager].backgroundSessionCompletionHandler = completionHandler;}
And then make sure your NSURLSessionDelegate
calls this handler on the main thread when the background session is done:
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { if (self.backgroundSessionCompletionHandler) { dispatch_async(dispatch_get_main_queue(), ^{ self.backgroundSessionCompletionHandler(); self.backgroundSessionCompletionHandler = nil; }); }}
This is only called if some of the uploads finished in the background.
There are a few moving parts, as you can see, but that's basically what's entailed.