一、小文件下载,eg:图片
NSURL *url = [NSURL URLWithString:@"图片的地址XXX"];NSData *data = [NSData dataWithContentsOfURL:url];
[UIImage imageWithData:data]
例如:NSURLConnection发送一个异步的Get请求
NSURL* url = [NSURL URLWithString:@"需要下载的地址XXXX"];[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:url] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {self.imageView.image = [UIImage imageWithData:data]; }];
上面的方法如果下载大文件会很消耗内存,所以尽量下载小文件
二、NSURLConnection下载大文件
// 发送请求去下载 (创建完conn对象后,会自动发起一个异步请求)
[NSURLConnection connectionWithRequest:request delegate:self];
实现以下代理方法
@property (weak, nonatomic) IBOutlet UIProgressView *myPregress;
@property (nonatomic,strong) NSMutableData* fileData;/** * 文件的总长度 */
@property (nonatomic, assign) long long totalLength;
/** * 1.接收到服务器的响应就会调用 * *
@param response 响应 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.fileData = [NSMutableData data];
// 获取要下载的文件的大小
self.totalLength = response.expectedContentLength;
}
/** * 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
* * @param data 这次返回的数据 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.fileData appendData:data];
self.myPregress.progress = (double)self.fileData.length / self.totalLength;
}
/** * 3.加载完毕后调用(服务器的数据已经完全返回后) */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
// 拼接文件路径
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [cache stringByAppendingPathComponent:response.suggestedFilename];
// 写到沙盒中
[self.fileData writeToFile:file atomically:YES];
}
response.expectedContentLength 这句代码下载的文件的大小 。
response.suggestedFilename 这句代表获取下载的文件名
用来接受文件的NSMutableData一直都在内存中,会随着文件的下载一直变大,
合理的方式在我们获取一部分data的时候就写入沙盒中,然后释放内存中的data。
这里要用到NSFilehandle这个类,这个类可以实现对文件的读取、写入、更新。
下面总结了一些常用的NSFileHandle的方法,在这个表中,fh是一个NSFileHandle对象,data是一个NSData对象,path是一个NSString 对象,offset是易额Unsigned long long变量。
在接受到响应的时候就在沙盒中创建一个空的文件,然后每次接收到数据的时候就拼接到这个文件的最后面,通过- (unsigned long long)seekToEndOfFile; 这个方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
// 文件路径
NSString* ceches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString* filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename];
// 创建一个空的文件到沙盒中
NSFileManager* mgr = [NSFileManager defaultManager];
[mgr createFileAtPath:filepath contents:nil attributes:nil];
// 创建一个用来写数据的文件句柄对象
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
// 获得文件的总大小
self.totalLength = response.expectedContentLength;}
/** * 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次) * * @param data 这次返回的数据 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
// 移动到文件的最后面
[self.writeHandle seekToEndOfFile];
// 将数据写入沙盒
[self.writeHandle writeData:data];
// 累计写入文件的长度
self.currentLength += data.length;
// 下载进度
self.myPregress.progress = (double)self.currentLength / self.totalLength;}
/** * 3.加载完毕后调用(服务器的数据已经完全返回后) */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
self.currentLength = 0; self.totalLength = 0;
// 关闭文件
[self.writeHandle closeFile]; self.writeHandle = nil;
}
NSURLConnection 只提供了一个cancel方法,这并不是暂停,而是取消下载任务。如果要实现断点下载必须要了解HTTP协议中请求头的Range。
不难看出,通过设置请求头的Range我们可以指定下载的位置、大小。
那么我们这样设置bytes=500- 从500字节以后的所有字节,
只需要在didReceiveData中记录已经写入沙盒中文件的大小(self.currentLength),
把这个大小设置到请求头中,因为第一次下载肯定是没有执行过didReceive方法,self.currentLength也就为0,也就是从头开始下。
#pragma mark --按钮点击事件
- (IBAction)btnClicked:(UIButton *)sender {
// 状态取反
sender.selected = !sender.isSelected;
// 断点续传 // 断点下载
if (sender.selected) {
// 继续(开始)下载
// 1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost:8080//term_app/hdgg.zip"];
// 2.请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求头
NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
// 3.下载
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
} else {
// 暂停
[self.connection cancel];
self.connection = nil;
}
}
三、NSURLSession下载方式(与NSURLConnection不同的是,NSURLSession需要手动设置下载)使用NSURLSession就非常简单了,不需要去考虑什么边下载边写入沙盒的问题
NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];
// 得到session对象
NSURLSession* session = [NSURLSession sharedSession];
// 创建任务
NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
}];
// 开始任务
[downloadTask resume];
是不是跟NSURLConnection很像,但仔细看会发现回调的方法里面并没用NSData传回来,多了一个location,顾名思义,location就是下载好的文件写入沙盒的地址,打印一下发现下载好的文件被自动写入的temp文件夹下面了。
不过在下载完成之后会自动删除temp中的文件,所有我们需要做的只是在回调中把文件移动(或者复制,反正之后会自动删除)到caches中。
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// response.suggestedFilename : 建议使用的文件名,一般跟服务器端的文件名一致
NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];
// 将临时文件剪切或者复制Caches文件夹
NSFileManager *mgr = [NSFileManager defaultManager];
// AtPath : 剪切前的文件路径
// ToPath : 剪切后的文件路径
[mgr moveItemAtPath:location.path toPath:file error:nil];
以上方法不能监听下载进度,实现NSURLSession的delegate方法就能监听下载进度
#pragma mark -- NSURLSessionDownloadDelegate
/** * 下载完毕会调用 * * @param location 文件临时地址 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location{
}
/** * 每次写入沙盒完毕调用 * 在这里面监听下载进度,totalBytesWritten/totalBytesExpectedToWrite * * @param bytesWritten 这次写入的大小 * @param totalBytesWritten 已经写入沙盒的大小 * @param totalBytesExpectedToWrite 文件总大小 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
self.pgLabel.text = [NSString stringWithFormat:@"下载进度:%f",(double)totalBytesWritten/totalBytesExpectedToWrite];
}
/** * 恢复下载后调用, */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffsetexpectedTotalBytes:(int64_t)expectedTotalBytes{
}
任务取消的方法
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
取消操作以后会调用一个Block,并传入一个resumeData,该参数包含了继续下载文件的位置信息。也就是说,当你下载了10M得文件数据,暂停了。那么你下次继续下载的时候是从第10M这个位置开始的,而不是从文件最开始的位置开始下载。因而为了保存这些信息,所以才定义了这个NSData类型的这个属性:resumeData。这个data只包含了url跟已经下载了多少数据,不会很大,不用担心内存问题。
另外,session还提供了通过resumeData来创建任务的方法
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
示例程序下载:
https://github.com/hongfenglt/HFDownLoad