如果在网速一定的情境下,大文件(目前指的是100M以上的文件)的下载对用户来说是一段不短的时间,用户体验不是很好。
分块下载文件实现原理
如果要实现文件的分段下载,我们首先需要知道要下载的文件的大小,这里需要向服务器发送HTTP请求,请求方法为HEAD,这样服务器只会给客户端返回response的包头信息,不会发送数据信息,然后我们通过包头信息中的Content-Length字段可以得到要下载的文件的总长度。
- (void)getFileTotalLengthWithURL:(NSString *)url
completion:(void(^)(NSInteger length))completion{
NSURL *URL = [NSURL URLWithString:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
request.HTTPMethod = @"HEAD";
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *tmpResponse = (NSHTTPURLResponse *)response;
NSLog(@"allHeaderFields:%@", tmpResponse.allHeaderFields);
}
NSInteger fileTotalLength = response.expectedContentLength;
completion(fileTotalLength);
}];
[dataTask resume];
}
获取到要下载的文件的总长度之后,在本地沙盒中创建一个同样大小的文件
- (void)multiDownloadWithFileLength:(NSInteger)fileLength url:(NSURL *)url{
_wholeFileLength = fileLength;
NSString *filePath = [self filePathWithFileName:url.lastPathComponent];
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:filePath]) {
[fm removeItemAtPath:filePath error:nil];
}
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
_filePath = filePath;
_fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[_fileHandle truncateFileAtOffset:fileLength];
NSBlockOperation *addOperationOP = [NSBlockOperation blockOperationWithBlock:^{
while (_completedLength < fileLength) {
long long startSize = _completedLength;
long long endSize = startSize+blockSize;
if (endSize > fileLength) {
endSize = fileLength - 1;
_completedLength = fileLength;
} else {
_completedLength += blockSize;
}
//一个operation对应一个downloadTask
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSString *range=[NSString stringWithFormat:@"bytes=%lld-%lld", startSize, endSize];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:range forHTTPHeaderField:@"Range"];
NSLog(@"requestHeader:%@", request.allHTTPHeaderFields);
NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request];
[task resume];
}];
[_queue addOperation:operation];
}
}];
[_queue addOperation:addOperationOP];
}
每个请求块的大小
#define blockSize 1024*1024
新建一个下载队列,循环发送请求,在请求头中设置Range字段
NSOperationQueue *queue = [NSOperationQueue currentQueue];
NSBlockOperation *addOperationOP = [NSBlockOperation blockOperationWithBlock:^{
while (_completedLength < fileLength) {
long long startSize = _completedLength;
long long endSize = startSize+blockSize;
if (endSize > fileLength) {
endSize = fileLength - 1;
_completedLength = fileLength;
} else {
_completedLength += blockSize;
}
//一个operation对应一个downloadTask
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSString *range=[NSString stringWithFormat:@"bytes=%lld-%lld", startSize, endSize];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:range forHTTPHeaderField:@"Range"];
NSLog(@"requestHeader:%@", request.allHTTPHeaderFields);
NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request];
[task resume];
}];
[queue addOperation:operation];
}
}];
[queue addOperation:addOperationOP];
在代理方法中将获取到的数据写到已经创建好的空文件的对应的位置中
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
DLQData *tmpReceivedData = [[DLQData alloc] init];
NSInteger startSize = 0;
NSInteger endSize = 0;
if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *tmpResponse = (NSHTTPURLResponse *)downloadTask.response;
NSDictionary *dic = tmpResponse.allHeaderFields;
NSLog(@"diiiiiic: %@", dic[@"Content-Range"]);
NSString *fileRange = dic[@"Content-Range"];
fileRange = [fileRange stringByReplacingOccurrencesOfString:@"bytes" withString:@""];
fileRange = [fileRange stringByReplacingOccurrencesOfString:@" " withString:@""];
NSArray *aTmp1 = [fileRange componentsSeparatedByString:@"/"];
NSArray *aTmp2 = @[];
if (aTmp1.count) {
NSString *tmpStr = aTmp1[0];
aTmp2 = [tmpStr componentsSeparatedByString:@"-"];
if (aTmp1.count >= 2) {
NSString *startSizeStr = aTmp2[0];
NSString *endSizeStr = aTmp2[1];
startSize = startSizeStr.integerValue;
endSize = endSizeStr.integerValue;
tmpReceivedData.data = [NSData dataWithContentsOfURL:location];
tmpReceivedData.startSize = startSize;
tmpReceivedData.endSize = endSize;
[_fileHandle seekToFileOffset:tmpReceivedData.startSize];
[_fileHandle writeData:tmpReceivedData.data];
[_fileData appendData:tmpReceivedData.data];
double progress = _fileData.length/_wholeFileLength;
progress = progress >= 1 ? 1 : progress;
if (progress == 1) {
NSLog(@"分段下载完成");
NSLog(@"downloadProgress:%f", progress);
[_operationQueue cancelAllOperations];
_operationQueue = nil;
if ([self.delegate respondsToSelector:@selector(multiDownloadDidFinished:)] && [self.delegate respondsToSelector:@selector(multiDownloadProgress:)]) {
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
[self.delegate multiDownloadProgress:progress];
[self.delegate multiDownloadDidFinished:_filePath];
}];
}
}else{
if ([self.delegate respondsToSelector:@selector(multiDownloadProgress:)]) {
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
[self.delegate multiDownloadProgress:progress];
}];
}
}
}
}
}
}
参考文章
iOS开发网络篇—多线程断点下载