Use NSURLSessionDownloadTask to realize the advantages and disadvantages of downloading

For downloading files in iOS, Apple provides a class called NSURLSessionDownloadTask to do this. NSURLSessionDownloadTask supports block download and proxy download. Block downloading is not suitable for large files because it waits until the file is downloaded before calling back the Block argument behind the completionHandler to get location, response, error, and so on. In this way, for large files, we can not get the progress of the file in real time during the download process. For the proxy download method, it is perfect to solve this problem, we can obtain the real-time download progress in the proxy method.

For the breakpoint continuingly NSURLSessionDownloadTask also provides cancelByProducingResumeData: ^ (NSData * _Nullable resumeData) in order to save the data when cancel the download. And provides downloadTaskWithResumeData: nonnull NSData *) to undertake according to resumeData breakpoint continuingly. However, these methods can only handle cases where the user manually clicks the “Cancel download” button. In practice, the user simply exits the application without hitting the cancel button. If this is the case, resumeData is not stored, making it impossible to restart the application and continue downloading.

Use NSURLSessionDataTask to achieve large file download

In view of the above situation, in practical application, we mostly use NSURLSessionDataTask agent to complete the large file download function. The code is as follows:

#pragma mark - getter

- (NSURLSessionDataTask *)dataTask
{
    if(! _dataTask) {/ / create folders _directoryPath = [[NSSearchPathForDirectoriesInDomains (NSCachesDirectory NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"YDDownloads"];
        if(! [[NSFileManager defaultManager] fileExistsAtPath:_directoryPath]) { [[NSFileManager defaultManager] createDirectoryAtPath:_directoryPath withIntermediateDirectories:YES attributes:nil error:nil]; } NSString *fileName = [[NSUserDefaults standardUserDefaults] objectForKey:@"fileName"];
        _filePath = [self.directoryPath stringByAppendingPathComponent:fileName];
        NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:_filePath error:nil];
        _receivedLength = [attributes[@"NSFileSize"] longLongValue]; / / create NSURLSession _session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V6.3.0.dmg"];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        NSString *range = [NSString stringWithFormat:@"bytes=%zd-", _receivedLength];
        [request setValue:range forHTTPHeaderField:@"Range"];
        _dataTask = [_session dataTaskWithRequest:request];
    }
    return _dataTask;
}

#pragma Mark - Event response// start/resume download - (IBAction)startTask:(id)sendder {[self.datatask resume]; } // Suspend the task - (IBAction)suspendTask:(id)sender
{
    [self.dataTask suspend]; } // cancelTask - (IBAction)cancelTask:(id)sender {[self.datatask cancel]; self.dataTask = nil; }#pragma mark - NSURLSessionDataDelegate- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *) response completionHandler (void (^) (NSURLSessionResponseDisposition disposition)) completionHandler {/ / _filePath created files  = [self.directoryPath stringByAppendingPathComponent:response.suggestedFilename];if(! [[NSFileManager defaultManager] fileExistsAtPath:_filePath]) { [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil]; [[NSUserDefaults standardUserDefaults]setObject:response.suggestedFilename forKey:@"fileName"]; } _lastDate = [NSDate date]; _expectedLength = response.expectedContentLength + self.receivedLength; / / file handle to specified path _fileHandle = [NSFileHandle fileHandleForWritingAtPath: _filePath]; [_fileHandle seekToEndOfFile]; completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // writeData [_fileHandle writeData:data]; _receivedLength += data.length; CGFloat taskProgress = _receivedLength * 1.0 / _expectedLength; NSLog(@"Download progress: %.2f", taskProgress); _accumulateLength += data.length;if([[NSDate date] timeIntervalSinceDate:_lastDate] >= 1) {CGFloat taskSpeed = _accumulateLength * 1.0; NSLog(@"Download speed: %.2fm /s", taskSpeed / 1024 / 1024); _lastDate = [NSDate date]; _accumulateLength = 0; } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {// closeFile handle [_fileHandle closeFile]; _fileHandle = nil; // Empty the argument self.datatask = nil; _accumulateLength = 0; }Copy the code

On the one hand, we can get information about file download progress and speed in real time in the proxy method, and on the other hand, we can process the breakpoint continuation in the request header, which is in the above code

NSString *range = [NSString stringWithFormat:@"bytes=%zd-", _receivedLength];
[request setValue:range forHTTPHeaderField:@"Range"];
Copy the code

As long as you get the local file size first, you can request the rest of the data based on the current file size. Different from NSURLSessionDownloadTask, download using NSURLSessionDataTask requires us to manually write data. Here, the file handle class NSFileHandle is used to achieve this. Before writing, call seekToEndOfFile to point the file handle to the end of the file. Otherwise, the file location may be wrong. Also remember to call closeFile to close the file handle once the download is complete.

conclusion

Often, we also encounter queue downloads at work. For queue downloads, the usual approach is to use arrays. About the specific implementation details, I wrote a demo, interested friends can go to my Github view: Github address