IOS 7 NSURLSession Download multiple files in the background

I want to upload a list of files using NSUrlSession.

I have a variable for counting successful downloads @property (nonatomic) int downloadsSuccessfulCounter; . When downloading files, the Download Button disabled. When the counter is equal to the size of the download list, I turn on the button again and set the counter to 0. I do this in the method:

 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { ... [[NSOperationQueue mainQueue] addOperationWithBlock:^ { downloadsSuccessfulCounter++; if(downloadsSuccessfulCounter == self.downloadList.count) { NSLog(@"All downloads finished"); [self.syncButton setEnabled:YES]; downloadsSuccessfulCounter = 0; } }]; 

}

Everything works fine, but when I open the ViewController again, I get the message A background URLSession with identifier com.myApp already exists! . The counter is not set to 0, and user interface elements (UIButtons, UILabels) do not respond.

I assume the problem is that NSURLSession is still open, but I'm not sure how this works.

I tried all the tutorials, but 99% of them are only for downloading 1 file, not more than 1 ... Any ideas?

Here is my code:

 ... @property (nonatomic, strong) NSURLSession *session; ... - (void)viewDidLoad { [super viewDidLoad]; appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; self.downloadList = [[NSMutableArray alloc] init]; NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.myApp"]; sessionConfiguration.HTTPMaximumConnectionsPerHost = 5; self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil]; } 

When I click the Download Button , I call this method (I have a Downloadable object that contains NSURLSessionDownloadTask ):

 -(void)startDownload { for (int i=0; i<[self.downloadList count]; i++) { Downloadable *d = [self.downloadList objectAtIndex:i]; if (!d.isDownloading) { if (d.taskIdentifier == -1) { d.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:d.downloadSource]]; }else { d.downloadTask = [self.session downloadTaskWithResumeData:fdi.taskResumeData]; } d.taskIdentifier = d.downloadTask.taskIdentifier; [d.downloadTask resume]; d.isDownloading = YES; } } } 

When the application is in the background:

 -(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ AppDelegate *appDelegate = [UIApplication sharedApplication].delegate; [self.session 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(); UILocalNotification *localNotification = [[UILocalNotification alloc] init]; localNotification.alertBody = @"All files downloaded"; [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification]; }]; } } }]; } 
+5
source share
1 answer

So, as I mentioned in my comments, the problem is that each file requires a unique NSURLSession, and each NSURLSession requires an NSURLSessionConfiguration with a unique identifier.

I think that you were close - and probably more appropriate than me, in certain aspects ... You just need to create a structure to transfer unique identifiers to unique configurations in order to populate unique sessions (say 10x fast).

Here is what I did:

/ * * Retrieves the list of files to upload * Also uses the size of this list to instantiate items * In my case, I upload a text file with a symbol with the names of the files I want to upload * /

 - (void) getMediaList { NSString *list = @"http://myserver/media_list.txt"; NSURLSession *session = [NSURLSession sharedSession]; // <-- BASIC session [[session dataTaskWithURL:[NSURL URLWithString:list] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { NSString *stringFromData = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; // Populate Arrays REMOTE_MEDIA_FILE_PATHS = [stringFromData componentsSeparatedByString:@"\n"]; [self instantiateURLSessions:[REMOTE_MEDIA_FILE_PATHS count]]; // Start First File [self getFile:[REMOTE_MEDIA_FILE_PATHS objectAtIndex:downloadCounter]:downloadCounter]; // this variable is 0 at the start }] resume]; } 

/ * * This sets the arrays of configurations and sessions for the correct size * It also gives a unique identifier to each of them * /

 - (void) instantiateURLSessions : (int) size { NSMutableArray *configurations = [NSMutableArray array]; NSMutableArray *sessions = [NSMutableArray array]; for (int i = 0; i < size; i++) { NSString *index = [NSString stringWithFormat:@"%i", i]; NSString *UniqueIdentifier = @"MyAppBackgroundSessionIdentifier_"; UniqueIdentifier = [UniqueIdentifier stringByAppendingString:index]; [configurations addObject: [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:UniqueIdentifier]]; [sessions addObject:[NSURLSession sessionWithConfiguration: [configurations objectAtIndex:i] delegate: self delegateQueue: [NSOperationQueue mainQueue]]]; } NSURL_BACKGROUND_CONFIGURATIONS = [NSArray arrayWithArray:configurations]; NSURL_BACKGROUND_SESSIONS = [NSArray arrayWithArray:sessions]; } 

/ * * This sets up the “Download” task for each file based on the index of the array * It also concatenates the path to the actual file * /

 - (void) getFile : (NSString*) file :(int) index { NSString *fullPathToFile = REMOTE_MEDIA_PATH; // Path To Server With Files fullPathToFile = [fullPathToFile stringByAppendingString:file]; NSURL *url = [NSURL URLWithString:fullPathToFile]; NSURLSessionDownloadTask *downloadTask = [[NSURL_BACKGROUND_SESSIONS objectAtIndex:index ] downloadTaskWithURL: url]; [downloadTask resume]; } 

/ * * Finally, in my delegate method, after the download is complete (after the file has been moved from temporary data), I check if I am complete and if I did not call the getFiles method again with the updated counter for the index * /

 -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { // Get the documents directory URL NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:LOCAL_MEDIA_PATH]; NSURL *customDirectory = [NSURL fileURLWithPath:dataPath]; // Get the file name and create a destination URL NSString *sendingFileName = [downloadTask.originalRequest.URL lastPathComponent]; NSURL *destinationUrl = [customDirectory URLByAppendingPathComponent:sendingFileName]; // Move the file NSError *error = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager moveItemAtURL:location toURL:destinationUrl error: &error]) { // List [self listCustomDirectory]; if(downloadCounter < [REMOTE_MEDIA_FILE_PATHS count] -1) { // Increment Counter downloadCounter++; // Start Next File [self getFile:[REMOTE_MEDIA_FILE_PATHS objectAtIndex:downloadCounter]:downloadCounter]; } else { // FINISH YOUR OPERATION / NOTIFY USER / ETC } } else { NSLog(@"Damn. Error %@", error); // Do Something Intelligent Here } } 
+3
source

Source: https://habr.com/ru/post/1202224/


All Articles