I have a ebook related app live on app store. This app downloads big files (ranging from ~100MB - 1GB) in background mode. Out of 100 users 10 users have reported problems with the download when they are downloading over cellular data. We checked the OS of the device and they have the latest 13.x update. below code we are using to download data in background mode.
#import "MyAccountViewController.h"
#interface MyAccountViewController ()<NSURLSessionDelegate>
{
NSURLSessionConfiguration *sessionConfiguration;
}
#property (nonatomic, strong) NSURLSession *backgroundSession;
#property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;
#end
- (void)viewDidLoad
{
sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: #“testdownloadIdentifier];
self.backgroundSession = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate: self delegateQueue [NSOperationQueue mainQueue]];
sessionConfiguration.discretionary = false;
}
- (IBAction)downloadBtnTapped:(id)sender
{
NSURL *sourceUrl = [NSURL URLWithString:#“http://files.infogridpacific.com/ss/igp-twss.epub”];
self.downloadTask = [self.backgroundSession downloadTaskWithURL: sourceUrl];
[self.downloadTask resume];
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(#"INSIDE_didWriteData ");
}
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSLog(#"INSIDE_didFinishDownloadingToURL ");
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSInteger code = [(NSHTTPURLResponse *)task.response statusCode];
NSLog(#"INSIDE_didCompleteWithError code : %ld",code);
}
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
[self.backgroundSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([downloadTasks count] == 0) {
if (appDelegate.backgroundTransferCompletionHandler != nil) {
void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;
appDelegate.backgroundTransferCompletionHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler();
}];
}
}
}];
}
We have tried following
1. App Transport Security Settings —> Allow Arbitrary Loads —> YES
2. Tried downloading in foreground (it works perfectly on the affected devices)
3. Tried discretionary values YES/NO both but it does not have any effect.
In the devices where download is failing the delegate methods are not getting called.
Please suggest if there is any alternate way to download big files over cellular data.
Related
NSURLSession Delegate method
URLSessionDidFinishEventsForBackgroundURLSession is not Calling ?
I already enabled the Background Modes in project capabilities settings.
Here is the code
AppDelegate.h Method
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic, copy) void(^backgroundTransferCompletionHandler)();
#end
AppDelegate.m Method
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
self.backgroundTransferCompletionHandler = completionHandler;
}
ViewController.m Method
- (void)viewDidLoad
{
[super viewDidLoad];
//Urls
[self initializeFileDownloadDataArray];
NSArray *URLs = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
self.docDirectoryURL = [URLs objectAtIndex:0];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.GACDemo"];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 5;
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
NSUrlSession Method
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
// Check if all download tasks have been finished.
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([downloadTasks count] == 0) {
if (appDelegate.backgroundTransferCompletionHandler != nil) {
// Copy locally the completion handler.
void(^completionHandler)() = appDelegate.backgroundTransferCompletionHandler;
// Make nil the backgroundTransferCompletionHandler.
appDelegate.backgroundTransferCompletionHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Call the completion handler to tell the system that there are no other background transfers.
completionHandler();
// Show a local notification when all downloads are over.
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"All files have been downloaded!";
[[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}];
}
}
}];
}
I'm able to download all the files one by one but After downloading all the files, URLSessionDidFinishEventsForBackgroundURLSession method is not calling .
I have to perform some action method After Downloading all the files only.
These delegate methods won't get called if:
The app is already running when the tasks finish;
The app was terminated by double-tapping on the device's home button and manually killing it; or
If you fail to start a background NSURLSession with the same identifier.
So, the obvious questions are:
How was the app terminated? If not terminated, or if terminated incorrectly (e.g. you manually kill it by double-tapping on the home button), that will prevent these delegate methods from getting called.
Are you seeing handleEventsForBackgroundURLSession called at all?
Are you doing this on a physical device? This behaves differently on the simulator.
Bottom line, there's not enough here to diagnose the precise problem, but these are common reasons why that delegate method might not get called.
You later said:
Actually this is the first time I'm using NSURLSession class. My actual requirement is once the download (all the images) is completed then only I can retrieve the images from document directory and I can show in UICollectionView.
I'm following this tutorial from APPCODA. Here is the link http://appcoda.com/background-transfer-service-ios7
If that's your requirement, then background NSURLSession might be overkill. It's slower than standard NSURLSession, and more complicated. Only use background sessions if you really need large downloads to continue in the background after the app is suspended/terminated.
That tutorial you reference seems like a passable introduction to a pretty complicated topic (though I disagree with the URLSessionDidFinish... implementation, as discussed in comments). I would do something like:
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
// log message so we can see completion in device log; remove this once you're done testing the app
NSLog(#"%s", __FUNCTION__);
// Since you may be testing whether the terminated app is awaken when the
// downloads are done, you might want to post local notification.
// (Otherwise, since you cannot use the debugger, you're just staring
// at the device console hoping you see your log messages.) Local notifications
// are very useful in testing this, so you can see when this method is
// called, even if the app wasn't running. Obviously, you have to register
// for local notifications for this to work.
//
// UILocalNotification *notification = [[UILocalNotification alloc] init];
// notification.fireDate = [NSDate date];
// notification.alertBody = [NSString stringWithFormat:NSLocalizedString(#"Downloads done", nil. nil)];
//
// [[UIApplication sharedApplication] scheduleLocalNotification:notification];
// finally, in `handleEventsForBackgroundURLSession` you presumably
// captured the `completionHandler` (but did not call it). So this
// is where you'd call it on the main queue. I just have a property
// of this class in which I saved the completion handler.
dispatch_async(dispatch_get_main_queue(), ^{
if (self.savedCompletionHandler) {
self.savedCompletionHandler();
self.savedCompletionHandler = nil;
}
});
}
The question in my mind is whether you really want background session at all if you're just downloading images for collection view. I'd only do that if there were so many images (or they were so large) that they couldn't be reasonably downloaded while the app was still running.
For the sake of completeness, I'll share a full demonstration of background downloads below:
// AppDelegate.m
#import "AppDelegate.h"
#import "SessionManager.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
// other app delegate methods implemented here
// handle background task, starting session and saving
// completion handler
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
[SessionManager sharedSession].savedCompletionHandler = completionHandler;
}
#end
And
// SessionManager.h
#import UIKit;
#interface SessionManager : NSObject
#property (nonatomic, copy) void (^savedCompletionHandler)();
+ (instancetype)sharedSession;
- (void)startDownload:(NSURL *)url;
#end
and
// SessionManager.m
#import "SessionManager.h"
#interface SessionManager () <NSURLSessionDownloadDelegate, NSURLSessionDelegate>
#property (nonatomic, strong) NSURLSession *session;
#end
#implementation SessionManager
+ (instancetype)sharedSession {
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (instancetype)init {
self = [super init];
if (self) {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"foo"];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
}
return self;
}
- (void)startDownload:(NSURL *)url {
[self.session downloadTaskWithURL:url];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(#"%s: %#", __FUNCTION__, downloadTask.originalRequest.URL.lastPathComponent);
NSError *error;
NSURL *documents = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:false error:&error];
NSAssert(!error, #"Docs failed %#", error);
NSURL *localPath = [documents URLByAppendingPathComponent:downloadTask.originalRequest.URL.lastPathComponent];
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:localPath error:&error]) {
NSLog(#"move failed: %#", error);
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
NSLog(#"%s: %# %#", __FUNCTION__, error, task.originalRequest.URL.lastPathComponent);
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
NSLog(#"%s", __FUNCTION__);
// UILocalNotification *notification = [[UILocalNotification alloc] init];
// notification.fireDate = [NSDate date];
// notification.alertBody = [NSString stringWithFormat:NSLocalizedString(#"Downloads done", nil. nil)];
//
// [[UIApplication sharedApplication] scheduleLocalNotification:notification];
if (self.savedCompletionHandler) {
self.savedCompletionHandler();
self.savedCompletionHandler = nil;
}
}
#end
And, finally, the view controller code that initiates the request:
// ViewController.m
#import "ViewController.h"
#import "SessionManager.h"
#implementation ViewController
- (IBAction)didTapButton:(id)sender {
NSArray *urlStrings = #[#"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
#"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
#"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
#"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
#"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
#"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"];
for (NSString *urlString in urlStrings) {
NSURL *url = [NSURL URLWithString:urlString];
[[SessionManager sharedSession] startDownload:url];
}
// explicitly kill app if you want to test background operation
//
// exit(0);
}
- (void)viewDidLoad {
[super viewDidLoad];
// if you're going to use local notifications, you must request permission
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
#end
As stated by Apple:
If an iOS app is terminated by the system and relaunched, the app can use the same identifier to create a new configuration object and session and retrieve the status of transfers that were in progress at the time of termination. This behavior applies only for normal termination of the app by the system. If the user terminates the app from the multitasking screen, the system cancels all of the session’s background transfers. In addition, the system does not automatically relaunch apps that were force quit by the user. The user must explicitly relaunch the app before transfers can begin again.
i am uploading multiple files using NSURLSessionUploadTask. I want to run same process in back ground and it works fine for current file. App is not suspended until current file got uploaded successfully. Now, when second files comes to upload, it not start uploading process until i again launch or app became active. Uploading is not falling in between thats good. Is there any way to start next uploading process when first finishes. Second uploading is start also in back ground but that works upto 3 min from app goes in back ground. Here i shows my code:
AppDelegate.h
#property (nonatomic, copy) void(^backgroundTransferCompletionHandler)();
AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application{
__block UIBackgroundTaskIdentifier backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
NSLog(#"Background Time:%f",[[UIApplication sharedApplication] backgroundTimeRemaining]);
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];
backgroundTaskIdentifier = backgroundTaskIdentifier;
`enter code here`}];
}
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
self.backgroundTransferCompletionHandler = completionHandler;
NSLog(#"handleEventsForBackgroundURLSession called");
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appDelegate2 = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"URLSessionDidFinishEventsForBackgroundURLSession call in app delegate");
[session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
NSLog(#"uploadTasks: %#",uploadTasks);
if ([uploadTasks count] == 0) {
if (appDelegate2.backgroundTransferCompletionHandler != nil) {
// Copy locally the completion handler.
void(^completionHandler)() = appDelegate2.backgroundTransferCompletionHandler;
// Make nil the backgroundTransferCompletionHandler.
appDelegate2.backgroundTransferCompletionHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Call the completion handler to tell the system that there are no other background transfers.
completionHandler();
NSLog(#"All tasks are finished");
}];
}
}
else{
}
}];
}
UploadView.m
- (NSURLSession *)backgroundSession {
static NSURLSession *session = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSInteger randomNumber = arc4random() % 1000000;
// NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:#"com.example.apple-samplecode.SimpleBackgroundTransfer.BackgroundSession%d",(int)randomNumber]];
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:[NSString stringWithFormat:#"com.upload.myapp.BackgroundSession%d",(int)randomNumber]];
sessionConfiguration.sessionSendsLaunchEvents = YES;
sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
// Define the Upload task
[sessionConfiguration setHTTPAdditionalHeaders: #{#"Accept": #"text/html"}];
sessionConfiguration.timeoutIntervalForRequest = 600.0;
// sessionConfiguration.networkServiceType = NSURLNetworkServiceTypeBackground;
// sessionConfiguration.discretionary = YES;
sessionConfiguration.allowsCellularAccess = YES;
session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
});
return session;
}
ViewDidLoad(){
....
self.uploadSession = [self backgroundSession];
....}
-UploadStart(){//here i am calling this function in loop. When first file got uploaded, control comes here for second task and then upload should continue.
self.uploadTask = [self.uploadSession uploadTaskWithRequest:requestUpload fromFile:[NSURL fileURLWithPath:tmpfile]]; // self.uploadTask is an object of NSURLSessionUploadTask
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(#"didReceiveData Task: %# upload complete", dataTask);
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) self.uploadTask.response;
if (httpResp.statusCode == 200) {
uploadedFiles++; //an int value
if(uploadedFiles==arrUpload.count){
//all files uploaded here
}
else{
//upload next file from here
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
NSLog(#"URLSessionDidFinishEventsForBackgroundURLSession called");
AppDelegate *appDelegate2 = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate2.backgroundTransferCompletionHandler != nil) {
// Copy locally the completion handler.
void(^completionHandler)() = appDelegate2.backgroundTransferCompletionHandler;
// Make nil the backgroundTransferCompletionHandler.
appDelegate2.backgroundTransferCompletionHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Call the completion handler to tell the system that there are no other background transfers.
completionHandler();
NSLog(#"All tasks are finished");
}];
}
}
Above code is completely working, but having problem of next file is not start to upload in back ground after 3 min of App background mode.
Any help will be appreciate. Thanks in advance... For more explanation contact me.
Currently I'm implementing a file download application.
In my application server there are around 2500 Resource files, I need to download those files from server to my document directory.
My Code:
#implementation DownloadManager
{
NSURLSession *session;
BOOL downloading;
}
#pragma mark - NSURLSessionDownloadDelegate
// Handle download completion from the task
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSInteger index = [self assetDownloadIndexForDownloadTask:downloadTask];
if (index < 0)
{
return;
}
DownloadHelper *movieDownload = _assetsToDownload[index];
// Copy temporary file
NSError * error;
[[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:[movieDownload localPath]] error:&error];
downloading = NO;
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
// Required delegate method
}
// Handle task completion
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
NSLog(#"Task %# failed: %#", task, error);
NSLog(#"Task %# Success: %#", task, error);
if ([_assetsToDownload count])
{
[_assetsToDownload removeObjectAtIndex:0];
}
downloading = NO;
if ([_assetsToDownload count])
{
[self downloadFiles];
}
else
{
[self downloadAssets];
}
}
// Handle progress update from the task
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSInteger index = [self assetDownloadIndexForDownloadTask:downloadTask];
if (index < 0) return;
// DownloadHelper *movieDownload = _assetsToDownload[index];
double progress = (double) (totalBytesWritten/1024) / (double) (totalBytesExpectedToWrite/1024);
dispatch_async(dispatch_get_main_queue(), ^{
// Showing progress
});
}
#pragma mark - Movie Download Handling & UI
// Helper method to get the index of a Asset from the array based on downloadTask.
- (NSInteger)assetDownloadIndexForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
NSInteger foundIndex = -1;
NSInteger index = 0;
for (DownloadHelper *asset in _assetsToDownload)
{
if (asset.downloadTask == downloadTask)
{
foundIndex = index;
break;
}
index++;
}
return foundIndex;
}
- (void)addAssetDownload
{
DownloadInfo *info = nil;
NSString *assetFolder = nil;
for (int index = 0; index<[_assets count]; index++)
{
info = [_assets objectAtIndex:index];
NSURL *url = [NSURL URLWithString:info.assetURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
DownloadHelper *assetDownload = [[DownloadHelper alloc] initWithURL:url downloadTask:downloadTask];
assetDownload.assetName = info.assetName;
if (info.categoryId == 1)
{
assetFolder = [self getImagePath:info.assetName];
}
else if (info.categoryId == 2)
{
assetFolder = [self getVideoPath:info.assetName];
}
else if (info.categoryId == 3)
{
//assetFolder = [self getDBPath:info.assetName];
}
else
{
assetFolder = [self filePath:info.assetName];
}
assetDownload.assetFolder = assetFolder;
[_assetsToDownload addObject:assetDownload];
}
}
// Initialize the download, session and tasks
- (void)initialize
{
for (DTEDownloadHelper *movieDownload in _assetsToDownload)
{
// Cancel each task
NSURLSessionDownloadTask *downloadTask = movieDownload.downloadTask;
[downloadTask cancel];
}
// Cancel all tasks and invalidate the session (also releasing the delegate)
[session invalidateAndCancel];
session = nil;
_assetsToDownload = [[NSMutableArray alloc] init];
// Create a session configuration passing in the session ID
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:#"DTEDownloadBackground"];
sessionConfiguration.discretionary = YES;
session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
[self addAssetDownload];
// Reset the UI
downloading = NO;
[self downloadFiles];
}
// Download handler
- (void)downloadFiles
{
if ([_assetsToDownload count] > 0)
{
// Acquire the appropriate downloadTask and respond appropriately to the user's selection
NSURLSessionDownloadTask * downloadTask = [_assetsToDownload[0] downloadTask];
if (downloadTask.state == NSURLSessionTaskStateCompleted)
{
// Download is complete. Play movie.
// NSURL *movieURL = [NSURL fileURLWithPath:[_assetsToDownload[0] localPath]];
}
else if (downloadTask.state == NSURLSessionTaskStateSuspended)
{
// If suspended and not already downloading, resume transfer.
if (!downloading)
{
[self showHUD:[NSString stringWithFormat:#"Downloading %#",[_assetsToDownload[0] assetName]]];
[downloadTask resume];
downloading = YES;
}
}
else if (downloadTask.state == NSURLSessionTaskStateRunning)
{
// If already downloading, pause the transfer.
[downloadTask suspend];
downloading = NO;
}
}
}
- (void)downloadAssets
{
_assets = [self retreiveAssets]; // Getting the resource details from the database
if (![_assets count])
{
// Hide progress
}
[self addAssetDownload];
[self downloadFiles];
}
#end
Issue :
Sometimes it downloads the first file and stops there, on next time onwards it is not downloading anything. I couldn't find the issue till now, I wasted almost a day because of this issue. Please help me to find the issue. Thanks in advance.
When using background sessions, old download requests can persist from session to session. Have you tried checking for old, outstanding background tasks with getTasksWithCompletionHandler? I had a bear of a time until I realized that when my app starts, it can get backlogged behind old background requests. And if you have any invalid requests sitting in that background session, everything can get a little backed up.
Also, is your app delegate handling the handleEventsForBackgroundURLSession method, re-instantiating the background session and saving that completionHandler that is passed to your app? And is the delegate of your NSURLSession calling that completion handler (presumably in URLSessionDidFinishEventsForBackgroundURLSession: method)? You want to make sure you clean up these background sessions. I don't see any this method in your code snippet, but perhaps you omitted it for the sake of brevity.
A discussion of this can be found in the Background Transfer Considerations section of the URL Loading System Programming Guide: Using NSURLSession guide. Also example of this is shown about 40 minutes into the WWDC 2013 What’s New in Foundation Networking video.
Using NSURLSessionDownloadTask was a mess to me. So finally I implemented a custom download manager using NSOperationQueue and blocks.
I have added this library to GitHub.
I am developing an iOS application for downloading file from server (<10MB). I use NSURLSession and NSURLSessionDownloadTask for downloading.
My problem is that, the download view controller is not the rootViewController. When I turn back to the root view controller, I could see the download progress still work (from NSLog). But when I move to download view controller again, I could not see my label updated according to the progress. In this case, how could I get current running background session of NSURLSession to update my status label? Or any other solutions?
//Start downloading
-(void)startDownload: (NSString *)url{
NSString *sessionId = MY_SESSION_ID;
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionId];
sessionConfiguration.HTTPMaximumConnectionsPerHost = 1;
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:url]];
[self.downloadTask resume];
}
//Delegate
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
if (totalBytesExpectedToWrite == NSURLSessionTransferSizeUnknown) {
NSLog(#"Unknown transfer size");
}
else{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSInteger percentage = (double)totalBytesWritten * 100 / (double)totalBytesExpectedToWrite;
self.percentageLabel.text = [NSString stringWithFormat:#"Downloading (%ld%%)", (long)percentage];
NSLog(#"Progress: %ld", (long)percentage);
}];
}
}
This is what I did in my code and it worked:
[self performSelectorOnMainThread:#selector(setuploadStatus:) withObject:[NSString stringWithFormat:#"Upload %ld%%", (long)percentage] waitUntilDone:NO];
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
NSInteger percentage = (double)totalBytesSent * 100 / (double)totalBytesExpectedToSend;
**[self performSelectorOnMainThread:#selector(setuploadStatus:) withObject:[NSString stringWithFormat:#"Upload %ld%%", (long)percentage] waitUntilDone:NO];**
NSLog(#"Upload %ld%% ",(long)percentage);
}
-(void) setuploadStatus : (NSString *) setStat {
[_notifyTextLabel setText:setStat];
}
I have used NSURLSession to download a single file and it is working fine,Now i have to download three files in background and also have to manage their progress in UIProgress.My code for single downloading is below..
- (IBAction)startBackground:(id)sender
{
// Image CreativeCommons courtesy of flickr.com/charliematters
NSString *url = #"http://farm3.staticflickr.com/2831/9823890176_82b4165653_b_d.jpg";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request];
[self setDownloadButtonsAsEnabled:NO];
self.imageView.hidden = YES;
// Start the download
[self.backgroundTask resume];
}
- (NSURLSession *)backgroundSession
{
static NSURLSession *backgroundSession = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:#"com.shinobicontrols.BackgroundDownload.BackgroundSession"];
backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
});
return backgroundSession;
}
#pragma mark - NSURLSessionDownloadDelegate methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
double currentProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndicator.hidden = NO;
self.progressIndicator.progress = currentProgress;
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
// Leave this for now
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
// We've successfully finished the download. Let's save the file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *documentsDirectory = URLs[0];
NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];
NSError *error;
// Make sure we overwrite anything that's already there
[fileManager removeItemAtURL:destinationPath error:NULL];
BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error];
if (success)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]];
self.imageView.image = image;
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
self.imageView.hidden = NO;
});
}
else
{
NSLog(#"Couldn't copy the downloaded file");
}
if(downloadTask == cancellableTask) {
cancellableTask = nil;
} else if (downloadTask == self.resumableTask) {
self.resumableTask = nil;
partialDownload = nil;
} else if (session == self.backgroundSession) {
self.backgroundTask = nil;
// Get hold of the app delegate
SCAppDelegate *appDelegate = (SCAppDelegate *)[[UIApplication sharedApplication] delegate];
if(appDelegate.backgroundURLSessionCompletionHandler) {
// Need to copy the completion handler
void (^handler)() = appDelegate.backgroundURLSessionCompletionHandler;
appDelegate.backgroundURLSessionCompletionHandler = nil;
handler();
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndicator.hidden = YES;
[self setDownloadButtonsAsEnabled:YES];
});
}
You can have multiple NSURLSessionDownloadTask use the same NSSession and each are executed one after the other on the same mainQueue.
when successfully downloaded they call:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
If you download a MP4 of 100 mB and a pic of 10KB then they will return in different orders into this method.
So to track what session and what downloadTask has returned in this
you need to set a string identifier BEFORE you call the web service
To distinguish/track NSURLSessions you set
session.configuration.identifier
to distinguish NSURLSessionDownloadTask use
downloadTask_.taskDescription
downloadTask_.taskDescription = [NSString stringWithFormat:#"%#",urlSessionConfigurationBACKGROUND_.identifier];
For example in my project I was downloading a number of user favourite videos and their thumbnails.
Each item had a id e.g.1234567
So I needed to make two calls for each favourite
so i created two identifiers
"1234567_VIDEO"
"1234567_IMAGE"
then called two ws calls and passed in the identifier in session.configuration.identifier
http://my.site/getvideo/1234567
"1234567_VIDEO"
http://my.site1/getimage/1234567
"1234567_IMAGE"
iOS7 will download the items in the background, app can go back to sleep
When done it calls
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
I then get
session.configuration.identifier
"1234567_IMAGE"
split it up and check the values
1234567_IMAGE
"1234567"
"_IMAGE" > item at location is a MP4 so save as /Documents/1234567.mp4
"_VIDEO" > item at location is a jpg so save as /Documents/1234567.jpg
If you have 3 urls to call you can have One NSURLSessionDownloadTask per NSSession
file 1 - NSSession1 > NSURLSessionDownloadTask1
file 2 - NSSession2 > NSURLSessionDownloadTask2
file 3 - NSSession3 > NSURLSessionDownloadTask3
This seems to work fine when the app is in the foreground.
But I had problems when using BACKGROUND TRANSFER with BACKGROUND FETCH.
The first NSSession > NSURLSessionDownloadTask1 would return and then none of the others would be called.
So safer to have multiple NSURLSessionDownloadTask in one NSSession1
file 1 - NSSession1 > NSURLSessionDownloadTask1
file 2 - > NSURLSessionDownloadTask2
file 3 - > NSURLSessionDownloadTask3
Be careful when doing this
call NSSession finishTasksAndInvalidate not invalidateAndCancel
//[session invalidateAndCancel];
[session finishTasksAndInvalidate];
invalidateAndCancel will stop the session and not finish the other download tasks