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.