1.NSData(小文件下载)
[NSData dataWithContentsOfURL:] 就是一种文件下载方式,Get请求
但是这种下载方式需要放到子线程中
NSURL *url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
2.NSURLConnection
2.1小文件下载(NSURLConnection)
通过NSURLConnection发送一个异步的Get请求,一次性将整个文件返回
NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"];
[NSURLConnection
sendAsynchronousRequest:[NSURLRequest requestWithURL:url]
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
//atomically:原子性
[data writeToFile:filePath atomically:YES];
}];
2.2 大文件下载(NSURLConnection)
大文件下载不能一次性返回整个文件,否则会造成内存泄漏,系统崩溃
// 发送请求去下载 (创建完conn对象后,会自动发起一个异步请求)
IOS9已经废弃:[NSURLConnection connectionWithRequest:request delegate:self];
需要实现NSURLConnectionDataDelegate 协议,常用的几个如下:
/**
* 请求失败时调用(请求超时、网络异常)
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
/**
* 1.接收到服务器的响应就会调用
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
/**
* 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
/**
* 3.加载完毕后调用(服务器的数据已经完全返回后)
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
didReceiveData方法会被频繁的调用,每次都会传回来一部分data
最终我们把每次传回来的数据合并成一个我们需要的文件。
通常合并文件是定义一个全局的NSMutableData,通过[mutableData appendData:data];来合并,最后将Data写入沙盒
代码如下:
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, strong) NSURLResponse *response;
@property (nonatomic, strong) NSMutableData *fileData;
@property (nonatomic, assign) long long fileLength; //>>文件长度
@property (nonatomic, strong) NSString *fileName; //>>文件名
- (void)viewDidLoad {
[super viewDidLoad];
_request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/objective-c.pdf"]];
//IOS9.0已经被废弃
[NSURLConnection connectionWithRequest:_request delegate:self];
}
/**
*接收到服务器的响应
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.response = response;
self.fileData = [NSMutableData data];
//获取下载文件大小
self.fileLength = response.expectedContentLength;
//获取文件名
self.fileName = response.suggestedFilename;
}
/**
*接收到服务器返回的数据(可能被调用多次)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[self.fileData appendData:data];
//更新画面中的进度
double progress = (double)self.fileData.length/self.fileLength;
self.progressView.progress = progress;
}
/**
*服务器返回数据完了
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cache stringByAppendingPathComponent:_fileName];
[self.fileData writeToFile:filePath atomically:YES];
}
但是有个致命的问题,内存!用来接受文件的NSMutableData一直都在内存中,会随着文件的下载一直变大,
合理的方式在我们获取一部分data的时候就写入沙盒中,然后释放内存中的data
要用到NSFilehandle这个类,这个类可以实现对文件的读取、写入、更新
每次接收到数据的时候就拼接文件后面,通过- (unsigned long long)seekToEndOfFile;方法
/**
*接收到服务器的响应
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// 文件路径
NSString* caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString* filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename];
// 创建一个空的文件到沙盒中
NSFileManager* fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath:filepath contents:nil attributes:nil];
// 创建一个用来写数据的文件句柄对象:用来填充数据
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
// 获得文件的总大小
self.fileLength = response.expectedContentLength;
}
/**
* 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// 移动到文件的最后面
[self.writeHandle seekToEndOfFile];
// 将数据写入沙盒
[self.writeHandle writeData:data];
// 累计写入文件的长度
self.currentLength += data.length;
// 下载进度
self.pregressView.progress = (double)self.currentLength / self.fileLength;
}
/**
* 3.加载完毕后调用(服务器的数据已经完全返回后)
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.currentLength = 0;
self.fileLength = 0;
// 关闭文件
[self.writeHandle closeFile];
self.writeHandle = nil;
}
下载过程中内存就会一直很稳定了,并且下载的文件也是没问题的
2.3 断点下载(NSURLConnection)
断点续传的response状态码为206
暂停/继续下载也是现在下载中必备的功能
NSURLConnection 只提供了一个cancel方法,这并不是暂停,而是取消下载任务。如果要实现断点下载必须要了解HTTP协议中请求头的Range
通过设置请求头的Range我们可以指定下载的位置、大小
Range实例:
bytes = 0-499 从0到499的头500个字节
bytes = 500-999 从500到999的第二个500个字节
bytes = 500- 500之后的所有字节
bytes = -500 最后500个字节
bytes = 0-599,700-899 同时指定多个范围
||
pragma mark --按钮点击事件,
- (IBAction)btnClicked:(UIButton *)sender {
// 状态取反
sender.selected = !sender.isSelected;
// 断点下载
if (sender.selected) { // 继续(开始)下载
// 1.URL
NSURL *url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/Code.pdf"];
// 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;
}
}
2.4 断点下载的全部代码
@interface NSURLConnectionViewController ()<NSURLConnectionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSURLRequest *request;
@property (nonatomic, strong) NSHTTPURLResponse *response;
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSFileHandle *fileHandle;
@property (nonatomic, assign) long long currentLength; //>>写入文件的长度
@property (nonatomic, assign) long long fileLength; //>>文件长度
@property (nonatomic, strong) NSString *fileName; //>>文件名
@end
@implementation NSURLConnectionViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@", NSHomeDirectory());
self.url = [NSURL URLWithString:@"http://10.167.20.151:8080/AdminConsole/help/objective-c.pdf"];
}
#pragma mark - NSURLConnectionDataDelegate
/**
*请求失败
*/
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"error");
}
/**
*接收到服务器的响应
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.response = (NSHTTPURLResponse *)response;
if (self.response.statusCode == 206) {//!!!断点续传的状态码为206
if (self.currentLength) {
return;
}
//获取下载文件大小
self.fileLength = response.expectedContentLength;
//获取文件名
self.fileName = response.suggestedFilename;
//文件路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [caches stringByAppendingPathComponent:_fileName];
//创建一个空的文件到沙盒
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createFileAtPath:filePath contents:nil attributes:nil];
//创建一个用来写数据的文件句柄
self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
}else{
[self.connection cancel];
self.connection = nil;
NSLog(@"该文件不存在");
}
}
/**
*接收到服务器返回的数据(可能被调用多次)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//移动到文件末尾
[self.fileHandle seekToEndOfFile];
//写入数据到文件
[self.fileHandle writeData:data];
self.currentLength += data.length;
//更新画面中的进度
double progress = (double)self.currentLength/self.fileLength;
self.progressView.progress = progress;
}
/**
*服务器返回数据完了
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
self.currentLength = 0;
self.fileLength = 0;
[self.fileHandle closeFile];
self.fileHandle = nil;
}
- (IBAction)pauseDownLoad:(UIButton *)sender {
//暂停<->开始转换
sender.selected = !sender.isSelected;
if (sender.selected) {//开始下载
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_url];
//设置请求头(GET)
NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}else{ //暂停
[self.connection cancel];
self.connection = nil;
}
}
@end
在下载过程中,为了提高效率,充分利用cpu性能,通常会执行多线程下载。。。待更新!!!
4. NSURLSession
生命周期的两种方式-1:系统代理-Block方式(流程简单-优先)
-2:指定代理类:delegate(流程复杂)
上面这种下载文件的方式确实比较复杂,要自己去控制内存写入相应的位置
iOS7推出了一个新的类NSURLSession,它具备了NSURLConnection所具备的方法,同时也比它更强大
NSURLSession 也可以发送Get/Post请求,实现文件的下载和上传。
在NSURLSesiion中,任何请求都可以被看做是一个任务。其中有三种任务类型
NSURLSessionDataTask : 普通的GET\POST请求
NSURLSessionDownloadTask : 文件下载
NSURLSessionUploadTask : 文件上传(很少用,一般服务器不支持)
4.1 NSURLSession 简单使用
NSURLSession发送请求非常简单,与connection不同的是,任务创建后不会自动发送请求,需要手动开始执行任务
// 1.得到session对象
NSURLSession* session = [NSURLSession sharedSession];
NSURL* url = [NSURL URLWithString:@""];
// 2.创建一个task,任务(GET)
NSURLSessionDataTask* dataTask =
[session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// data 为返回数据
}];
// 3.开始任务
[dataTask resume];
POST请求:可以自定义请求头
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
4.2 NSURLSession文件下载
使用NSURLSession就非常简单了,不需要去考虑什么边下载边写入沙盒的问题,苹果都帮我们做好了
只用将下载好的文件通过NSFileManager剪切到指定位置
需要用到NSURLSession的子类:NSURLSessionDownloadTask
NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/test.png"];
// 得到session对象
NSURLSession* session = [NSURLSession sharedSession];
// 创建任务
NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
//将文件迁移到指定路径下
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];
// 将临时文件剪切或者复制Caches文件夹
NSFileManager *fileManager = [NSFileManager defaultManager];
// AtPath : 剪切前的文件路径
// ToPath : 剪切后的文件路径
[fileManager moveItemAtPath:location.path toPath:file error:nil];
}];
// 开始任务
[downloadTask resume];
- location就是下载好的文件写入沙盒的地址
- 该方式无法监听下载进度
- 若要监听进度需要实现< NSURLSessionDownloadDelegate >协议,不能使用Block方式
/**
* 下载完毕会调用
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{...}
/**
* 每次写入沙盒完毕调用
* 在这里面监听下载进度,totalBytesWritten/totalBytesExpectedToWrite
*
* @param bytesWritten 这次写入的大小
* @param totalBytesWritten 已经写入沙盒的大小
* @param totalBytesExpectedToWrite 文件总大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{...}
/**
* 恢复下载后调用
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{...}
4.2 NSURLSession断点下载
:fa-font:1.暂停下载
resumeData,该参数包含了继续下载文件的位置信息
resumeData只包含了url跟已经下载了多少数据,不会很大,不用担心内存问题
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
!!!需要注意的是Block中循环引用的问题
__weak typeof(self) selfVc = self;
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
selfVc.resumeData = resumeData;
selfVc.downloadTask = nil;
}];
:fa-bold:2.恢复下载
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
4.3 NSURLSession断点下载所有代码
@interface NSURLSessionController () <NSURLSessionDownloadDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *myPregress;
//下载任务
@property (nonatomic, strong) NSURLSessionDownloadTask* downloadTask;
//resumeData记录下载位置
@property (nonatomic, strong) NSData* resumeData;
@property (nonatomic, strong) NSURLSession* session;
@end
@implementation NSURLSessionController
/**
* session的懒加载
*/
- (NSURLSession *)session
{
if (nil == _session) {
NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
/**
* 从0开始下载
*/
- (void)startDownload
{
NSURL* url = [NSURL URLWithString:@"http://10.167.20.151:8080/Admin/help/objective-c.pdf"];
// 创建任务
self.downloadTask = [self.session downloadTaskWithURL:url];
// 开始任务
[self.downloadTask resume];
}
/**
* 恢复下载
*/
- (void)resume
{
// 传入上次暂停下载返回的数据,就可以恢复下载
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume]; // 开始任务
self.resumeData = nil;
}
/**
* 暂停
*/
- (void)pause
{
__weak typeof(self) selfVc = self;
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
// resumeData : 包含了继续下载的开始位置\下载的url
selfVc.resumeData = resumeData;
selfVc.downloadTask = nil;
}];
}
#pragma mark -- NSURLSessionDownloadDelegate
/**
* 下载完毕会调用
*
* @param location 文件临时地址
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
//文件路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *file = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
// 将临时文件剪切或者复制Caches文件夹
NSFileManager *fileManager = [NSFileManager defaultManager];
// AtPath : 剪切前的文件路径
// ToPath : 剪切后的文件路径
[fileManager moveItemAtPath:location.path toPath:file error:nil];
NSLog(@"下载完成");
}
/**
* 每次写入沙盒完毕调用
* 在这里面监听下载进度,totalBytesWritten/totalBytesExpectedToWrite
*
* @param bytesWritten 这次写入的大小
* @param totalBytesWritten 已经写入沙盒的大小
* @param totalBytesExpectedToWrite 文件总大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
self.myPregress.progress = (double)totalBytesWritten/totalBytesExpectedToWrite;
}
/**
* 恢复下载后调用,
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
}
#pragma mark --按钮点击事件
- (IBAction)btnClicked:(UIButton *)sender {
// 按钮状态取反
sender.selected = !sender.isSelected;
if (nil == self.downloadTask) { // 开始(继续)下载
if (self.resumeData) { // 继续下载
[self resume];
}else{ // 从0开始下载
[self startDownload];
}
}else{ // 暂停
[self pause];
}
}