I had a very similar problem where the background task would start but then appear to pause. The task would then complete when the application came back into the foreground.
I verified that this was the case by logging the output from -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
I found that the way to get around this problem is to do with how you store, handle and execute your completion handlers.
In my case the process begins with
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
[self.fmStore performBackgroundRefresh:^(UIBackgroundFetchResult result) {
//Set application badge if new data is available
if (result==UIBackgroundFetchResultNewData) [UIApplication sharedApplication].applicationIconBadgeNumber++;
completionHandler(result);
}];
}
where a remote notification starts the download process.
The method that manages the download returns a value dependant on the availability of new data
-(void)performBackgroundRefresh:(void (^)(UIBackgroundFetchResult))completion{
if(newData) completion(UIBackgroundFetchResultNewData);
else completion(UIBackgroundFetchResultNoData);
}
At this point it returns back to the ApplicationDelegate where the completion handler is stored
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
//Store completion handler for background session
self.sessionCompletionHandler=completionHandler;
}
Finally, this piece of code is executed which calls the completion handler and creates the appropriate notifications
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
[session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if (![downloadTasks count]) {
FM_AppDelegate *appDelegate=(FM_AppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.sessionCompletionHandler) {
void (^completionHandler)() = appDelegate.sessionCompletionHandler;
appDelegate.sessionCompletionHandler = nil;
completionHandler();
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ContentRefreshNotification" object:Nil];
}];
}
}];
}
This back and forth process happens in my case as the NSURLSession exists in an object that is a property of the ApplicationDelegate. If you implement the NSURLSession as a property of the ApplicationDelegate itself then all of this code would exist in the same file.
Hope this helps but if you need more information please see these two tutorials 1 and 2 as my code is based on what I read in these.