iOS开发网络请求——大文件的多线程断点下载

时间:2022-11-08 21:38:40

  iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了。网络文件传输对移动客户端而言主要分为文件的上传和下载。作为开发者从技术角度会将文件分为小文件和大文件。小文件因为文件大小比较小导致传输所需时间少传输就快,因此不太容易影响用户体验,可用的技术就多。而大文件因为文件大小比较大导致传输时间长,因此就需要考虑到各种用户体验,比如避免在上传下载文件过程中阻塞主线程影响用户体验,就需要使用到多线程技术;为了给用户友好的进度提示,因此又需要开发中跟踪数据上传和下载数据的变化;为了提高用户体验,也需要考虑到断点续传的功能实现;而且大文件传输容易导致数据保持在内存中,又需要开发者处理内存中的数据;而为了处理多个文件或者压缩传输文件的数据大小,我们开发者还需要用到压缩和解压缩技术。根据不同的需求对大文件传输会有需要用到不同的解决方案,不过多线程断点续传是一个理想的在网络中传输大文件的方案。

小文件下载

  小文件的下载方式比较多,下面列出常用的下载方式:

    1.直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url方法;
    2.利用NSURLConnection发送一个HTTP请求去下载
    3.利用NSURLSession发送一个HTTP请求去下载
    4.利用AFNetworking发送一个HTTP请求去下载
    4.如果是下载图片,还可以利用SDWebImage框架
大文件下载最佳解决方案———多线程断点下载
  实现思路:下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。
  iOS开发网络请求——大文件的多线程断点下载
  项目中用到的主要类如下:
iOS开发网络请求——大文件的多线程断点下载
完成的实现代码如下:
  
YYViewController.m
#import "YYViewController.h"
#import "YYFileMultiDownloader.h" @interface YYViewController ()
@property (nonatomic, strong) YYFileMultiDownloader *fileMultiDownloader;
@end @implementation YYViewController
- (YYFileMultiDownloader *)fileMultiDownloader
{
if (!_fileMultiDownloader) {
_fileMultiDownloader = [[YYFileMultiDownloader alloc] init];
// 需要下载的文件远程URL
_fileMultiDownloader.url = @"http://192.168.1.200:8080/MJServer/resources/jre.zip";
// 文件保存到什么地方
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filepath = [caches stringByAppendingPathComponent:@"jre.zip"];
_fileMultiDownloader.destPath = filepath;
}
return _fileMultiDownloader;
} - (void)viewDidLoad
{
[super viewDidLoad]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.fileMultiDownloader start];
} @end

自定义一个基类

YYFileDownloader.h文件

#import <Foundation/Foundation.h>

@interface YYFileDownloader : NSObject
{
BOOL _downloading;
}
/**
* 所需要下载文件的远程URL(连接服务器的路径)
*/
@property (nonatomic, copy) NSString *url;
/**
* 文件的存储路径(文件下载到什么地方)
*/
@property (nonatomic, copy) NSString *destPath; /**
* 是否正在下载(有没有在下载, 只有下载器内部才知道)
*/
@property (nonatomic, readonly, getter = isDownloading) BOOL downloading; /**
* 用来监听下载进度
*/
@property (nonatomic, copy) void (^progressHandler)(double progress); /**
* 开始(恢复)下载
*/
- (void)start; /**
* 暂停下载
*/
- (void)pause;
@end

  YYFileDownloader.m文件

#import "YYFileDownloader.h"

@implementation YYFileDownloader
@end

下载器类继承自YYFileDownloader这个类

YYFileSingDownloader.h文件

#import "YYFileDownloader.h"

@interface YYFileSingleDownloader : YYFileDownloader
/**
* 开始的位置
*/
@property (nonatomic, assign) long long begin;
/**
* 结束的位置
*/
@property (nonatomic, assign) long long end;
@end

YYFileSingDownloader.m文件

#import "YYFileSingleDownloader.h"
@interface YYFileSingleDownloader() <NSURLConnectionDataDelegate>
/**
* 连接对象
*/
@property (nonatomic, strong) NSURLConnection *conn; /**
* 写数据的文件句柄
*/
@property (nonatomic, strong) NSFileHandle *writeHandle;
/**
* 当前已下载数据的长度
*/
@property (nonatomic, assign) long long currentLength;
@end @implementation YYFileSingleDownloader - (NSFileHandle *)writeHandle
{
if (!_writeHandle) {
_writeHandle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
}
return _writeHandle;
} /**
* 开始(恢复)下载
*/
- (void)start
{
NSURL *url = [NSURL URLWithString:self.url];
// 默认就是GET请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求头信息
NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin + self.currentLength, self.end];
[request setValue:value forHTTPHeaderField:@"Range"];
self.conn = [NSURLConnection connectionWithRequest:request delegate:self]; _downloading = YES;
} /**
* 暂停下载
*/
- (void)pause
{
[self.conn cancel];
self.conn = nil; _downloading = NO;
} #pragma mark - NSURLConnectionDataDelegate 代理方法
/**
* 1. 当接受到服务器的响应(连通了服务器)就会调用
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{ } /**
* 2. 当接受到服务器的数据就会调用(可能会被调用多次, 每次调用只会传递部分数据)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// 移动到文件的尾部
[self.writeHandle seekToFileOffset:self.begin + self.currentLength];
// 从当前移动的位置(文件尾部)开始写入数据
[self.writeHandle writeData:data]; // 累加长度
self.currentLength += data.length; // 打印下载进度
double progress = (double)self.currentLength / (self.end - self.begin);
if (self.progressHandler) {
self.progressHandler(progress);
}
} /**
* 3. 当服务器的数据接受完毕后就会调用
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// 清空属性值
self.currentLength = ; // 关闭连接(不再输入数据到文件中)
[self.writeHandle closeFile];
self.writeHandle = nil;
} /**
* 请求错误(失败)的时候调用(请求超时\断网\没有网, 一般指客户端错误)
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{ } @end

设计多线程下载器(利用HMFileMultiDownloader能开启多个线程同时下载一个文件)

一个多线程下载器只下载一个文件

YYFileMultiDownloader.h文件

#import "YYFileDownloader.h"

@interface YYFileMultiDownloader : YYFileDownloader

@end

YYFileMultiDownloader.m文件

#import "YYFileMultiDownloader.h"
#import "YYFileSingleDownloader.h" #define YYMaxDownloadCount 4 @interface YYFileMultiDownloader()
@property (nonatomic, strong) NSMutableArray *singleDownloaders;
@property (nonatomic, assign) long long totalLength;
@end @implementation YYFileMultiDownloader - (void)getFilesize
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
request.HTTPMethod = @"HEAD"; NSURLResponse *response = nil;
#warning 这里要用异步请求
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
self.totalLength = response.expectedContentLength;
} - (NSMutableArray *)singleDownloaders
{
if (!_singleDownloaders) {
_singleDownloaders = [NSMutableArray array]; // 获得文件大小
[self getFilesize]; // 每条路径的下载量
long long size = ;
if (self.totalLength % YYMaxDownloadCount == ) {
size = self.totalLength / YYMaxDownloadCount;
} else {
size = self.totalLength / YYMaxDownloadCount + ;
} // 创建N个下载器
for (int i = ; i<YYMaxDownloadCount; i++) {
YYFileSingleDownloader *singleDownloader = [[YYFileSingleDownloader alloc] init];
singleDownloader.url = self.url;
singleDownloader.destPath = self.destPath;
singleDownloader.begin = i * size;
singleDownloader.end = singleDownloader.begin + size - ;
singleDownloader.progressHandler = ^(double progress){
NSLog(@"%d --- %f", i, progress);
};
[_singleDownloaders addObject:singleDownloader];
} // 创建一个跟服务器文件等大小的临时文件
[[NSFileManager defaultManager] createFileAtPath:self.destPath contents:nil attributes:nil]; // 让self.destPath文件的长度是self.totalLengt
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
[handle truncateFileAtOffset:self.totalLength];
}
return _singleDownloaders;
} /**
* 开始(恢复)下载
*/
- (void)start
{
[self.singleDownloaders makeObjectsPerformSelector:@selector(start)]; _downloading = YES;
} /**
* 暂停下载
*/
- (void)pause
{
[self.singleDownloaders makeObjectsPerformSelector:@selector(pause)];
_downloading = NO;
} @end

补充说明:如何获得将要下载的文件的大小?

iOS开发网络请求——大文件的多线程断点下载

参考文章:http://www.cnblogs.com/wendingding/p/3947550.html