11

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?

Eric
  • 16,003
  • 15
  • 87
  • 139
  • Marc solved a similar question. http://stackoverflow.com/questions/370641/calculating-connection-download-speed – sangony Feb 11 '14 at 20:28
  • No, my question is about how to get `NSProgress` to do those calculations for you. – Eric Feb 11 '14 at 23:46
  • OK... what about this one http://stackoverflow.com/questions/19666883/update-uiprogressview-from-nsprogress – sangony Feb 12 '14 at 00:31

1 Answers1

17

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.

Community
  • 1
  • 1
vokilam
  • 10,153
  • 3
  • 45
  • 56
  • 1
    Thanks. So you have to make those calculations *yourself*! So far, `NSProgress` is just a glorified encapsulator for progress data... I guess `NSProgress` is useful when you chain several of them together..? – Eric Feb 14 '14 at 12:56
  • Yes, this is a prominent feature – vokilam Feb 15 '14 at 05:45
  • @Mojo66: the question was answered _thoroughly_. – Eric Mar 03 '16 at 13:57