5

I have implemented a download manager app targeting iOS 7+ using NSURLSession. The download manager has an enqueued list of files to be downloaded in priority order. The download works fine while the app is in background and delegate calls are getting called correctly. But when app goes in background,even though the download gets finished it takes too much time for

NSURLSession delegate:- **URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL

to get called. Some times the delegates are not called at all and when i come to foreground then the download task delegate is called.Any reason for this delay?

Mark J. Bobak
  • 13,720
  • 6
  • 39
  • 67
  • I am facing the same issue. I am passing an array of items, and the delegation method get fired only when i relaunch the app. – Nibin V Apr 29 '14 at 05:57
  • The callbacks are inconsistent and the downloads are taking too much time to begin.With small files i tried it out with simulator and downloaded some small image files.The callbacks for finished downloading are firing after the downloads are completed 2or 3 minutes ago.This causes lag for starting the next file to be downloaded.The problem is happening once i take my app into background.In foreground the same functions work well. – Jacob Davis Cherussery Apr 29 '14 at 06:35
  • Do you follow all the instructions in https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW44 (Background Transfer Considerations)? I mean, you use background session configuration and implement all necessary callbacks in appDelegate and NSURLSessionDelegate? – user2260054 Apr 29 '14 at 06:39
  • Yes i use the same functionalities.When i go to background first,the first file callback happens swiftly but as it moves to 3rd and 4th file it becomes too slow. – Jacob Davis Cherussery Apr 29 '14 at 09:01
  • I think clearing the completion handler in URLSessionDidFinishEventsForBackgroundURLSession is causing the problem. – Jacob Davis Cherussery Apr 30 '14 at 09:09

1 Answers1

0

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.

Sadiq Jaffer
  • 500
  • 2
  • 10
  • @Saddiq Jaffer In the NSURLSession video in WWDC 2014 Apple advice against the usae of one by one download and instead download whole files. – Jacob Davis Cherussery Jul 04 '14 at 13:04
  • That's exactly what I'm doing in the method `-(void)performBackgroundRefresh:(void (^)(UIBackgroundFetchResult))completion`. The code for downloading is not shown in my example as I was just trying to demonstrate how to handle and call completion handlers. – Sadiq Jaffer Jul 04 '14 at 13:14