Reputation: 1082
In my App I get JSON from a Web service. This JSON contains the urls of several files that I would like to download.
I want to download each file one by one (wait for the first download to finished until the second one starts and so an and so forth) using NSURLSessionDownloadTask
. I would also like to keep track of the total bytes written so I can update UI.
Thanks a lot in advance !
Upvotes: 2
Views: 1080
Reputation: 24237
NSURLSessionDownloadTask's as you well know do not play very nicely with NSOperationQueues unlike their counterpart the NSURLConnection (where it could be encapsulated inside an NSOperation).
One option would be to add all your urls to an array, and then inside the completionHandler of the task, simply queue the next item.
So you might create your tasks in a loop, call a progressBlock inside each tasks completion handler, store the tasks in an array, and queue the next task inside each tasks completion handler:
- (void)addRequestsWithURLs:(NSArray *)urls
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations, NSURLSessionDownloadTask *task,NSURL *location, NSURLResponse *response, NSError *error))progressBlock {
__block NSUInteger numberOfFinishedOperations = 0;
NSUInteger totalNumberOfOperations = [urls count];
for (NSString *url in urls) {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
__block NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
//downloadFileSomewhere
++numberOfFinishedOperations;
if (progressBlock) {
progressBlock(numberOfFinishedOperations, totalNumberOfOperations,task,destination != nil ? [NSURL fileURLWithPath:destination] : nil,response,error);
}
//queueNext
[self processCompletedTask:task];
}];
//stores an array of NSURLSessionTasks
[self.tasksWaitingToBeQueued addObject:task];
}
}
- (void)processCompletedTask:(NSURLSessionTask *)completedTask {
//clean up and queue next one
[self.tasksWaitingToBeQueued removeObject:completedTask];
nextTask = [self.tasksWaitingToBeQueued firstObject];
if (nextTask) {
[nextTask resume];
}
}
NOTE
In this example I show progress as the number of tasks completed and not the number of bytes, this is the recommended approach (its also simpler). To indicate progress using bytes you would need to know the total number of bytes to download beforehand (since you want to show a progress bar) and also implement the NSURLSession delegate and monitor the progress of each task, capture the bytes downloaded and update your block. If your server doesn't tell you the total number of bytes then you would probably need to do a HEAD request for every resource and aggregate the sizes. Personally this solution is way to complicated for what could simply be resolved by indicating progress as the number of files downloaded.
To achieve this might look something like this:
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
self.totalBytesWritten += totalBytesWritten;
NSUInteger totalProgressSoFar = self.totalBytesWritten;
NSUInteger totalExpectedBytes = self.totalExpectedBytes;
//you would need to capture some progress block locally - beware of retain cycles
self.progressBlock(totalProgressSoFar/totalExpectedBytes)
}
when you finish you should set the progressBlock to nil to prevent any retain cycles.
Upvotes: 2