Using NSProgress to find download speed and time remaining
ios, ios7, macos, nsprogress, osx-mavericks
Solution
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.
Problem
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?