According to the documentation for NSProgress
I see that -[NSProgress localizedAdditionalDescription]
can report download speed and time remaining, e.g.:
1.61 GB of 3.22 GB (2 KB/sec) — 2 minutes remaining
However, I'm not able to get those details when I associate an NSProgress
to a NSURLSessionDownloadTask
. Here's my code:
Downloader.h
@interface Downloader : NSObject
@property NSProgress *overallProgress;
-(void)startDownload;
@end
Downloader.m
- (void)startDownload {
self.overallProgress = [NSProgress progressWithTotalUnitCount:100];
[self.overallProgress setKind:NSProgressKindFile];
[self.overallProgress setUserInfoObject:NSProgressFileOperationKindKey forKey:NSProgressFileOperationKindDownloading];
[self.overallProgress becomeCurrentWithPendingUnitCount:100];
[self work1];
[self.overallProgress resignCurrent];
}
- (void)work1 {
NSProgress *firstTaskProgress = [NSProgress progressWithTotalUnitCount:1];
[firstTaskProgress setKind:NSProgressKindFile];
[firstTaskProgress setUserInfoObject:NSProgressFileOperationKindKey forKey:NSProgressFileOperationKindDownloading];
NSURL *downloadURL = [NSURL URLWithString:@"http://ipv4.download.thinkbroadband.com/200MB.zip"];
NSURL *destinationDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSURL *destinationURL = [destinationDirectory URLByAppendingPathComponent:[downloadURL lastPathComponent]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDownloadTask *fileDownloadTask =
[session downloadTaskWithURL:downloadURL
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error){
[[NSFileManager defaultManager] removeItemAtURL:destinationURL error:NULL];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:destinationURL error:nil];
[firstTaskProgress setCompletedUnitCount:1];
}];
[fileDownloadTask resume];
}
DownloadObserver.m
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
downloader = [Downloader new];
[downloader addObserver:self
forKeyPath:@"overallProgress.fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
[downloader startDownload];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@", [downloader.overallProgress localizedAdditionalDescription]);
}
This only prints out:
0 of 100
Zero KB of 100 bytes
How can I get localizedAdditionalDescription
to print the download speed and time remaining?
First of all, totalUnitCount
should correspond to the size of a file in bytes. During download process we change completedUnitCount
which reflects on localizedAdditionalDescription
. NSProgressKindFile
is a hint to format those values as file size.
To set progress properly we need to use NSURLSessionDownloadDelegate
methods instead of block handler. In -URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:
we have all information to initiate (totalBytesExpectedToWrite) and update (totalBytesWritten) progress.
static void *myContext = &myContext;
- (void)download {
NSURL *downloadURL = [NSURL URLWithString:@"http://ipv4.download.thinkbroadband.com/10MB.zip"];
NSURLSession *session =
[NSURLSession
sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
[[session downloadTaskWithURL:downloadURL] resume];
}
#pragma mark - url session download delegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (!_progress) {
_progress = [NSProgress progressWithTotalUnitCount:totalBytesExpectedToWrite];
_progress.kind = NSProgressKindFile;
[_progress addObserver:self forKeyPath:@"fractionCompleted" options:0 context:myContext];
}
_progress.completedUnitCount = totalBytesWritten;
//[_overallProgress setUserInfoObject:@1024 forKey:NSProgressEstimatedTimeRemainingKey];
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
_progress = nil;
NSLog(@"finished: %@", location);
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == myContext) {
self.label.text = [_progress localizedAdditionalDescription];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
In fact, NSProgress
doesn't do any calculations. It just formats localized description according to userInfo
and other class properties. See Constants section in documentation. You can play with these values and compare localized description outputs.
If you want to display remaining download time you can use NSProgressEstimatedTimeRemainingKey
. Bad news is that you need to calculate remaining time manually. Reference: How to estimate download time remaining (accurately)?
I tried to set NSProgressThroughputKey
which indicates the speed of data processing in bytes per second. I expected that NSProgress
would calculate remaining time, but it didn't happen.
See also AFNetworking sources: AFURLSessionManagerTaskDelegate
uses instances of NSProgress
for upload and download tasks.