iOS网络编程---根据URL下载网络文件的方法
iOS网络编程---根据URL下载网络文件的方法
下面是源码,注释掉的部分代码是一次性下载的功能(不推荐),正常运行的代码实现了:
- 功能:根据提供的URL下载网络文件
PS:其中有6个关键字,是实现该功能使用到的知识点,我在代码的后面做了必要的归纳和整理。
#define kReceivedTotal @"receivedTotal"
#define kTotal @"total"
#import "ViewController.h"@interface ViewController () <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
{//全局连接对象,用来取消下载NSURLConnection *_connection;double _totalLength; //获取数据总长度NSMutableData *_totalData; //接收到的数据double _receiveLength; //接收到的数据长度//判断是否正在下载BOOL isDownloading;//下载文件路径NSString *_filePath;
}
@end@implementation ViewController
- (void)viewDidLoad {[super viewDidLoad];//①:NSUserDefaults类
------------------- //读取本地化的plist文件,获取已下载数据量和文件总大小NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];_receiveLength = [[userDefault objectForKey:kReceivedTotal] doubleValue];_totalLength = [[userDefault objectForKey:kTotal] doubleValue];//作用:程序退出后会记录退出前下载的情况,下一次进入后会根据上一次退出时的情况改变progress和label的样式if (_totalLength > 0) {double progress = _receiveLength / _totalLength;_progressLabel.text = [NSString stringWithFormat:@"%.2f%%", progress*100];_progressLine.progress = progress;}}- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];// Dispose of any resources that can be recreated.
}#pragma mark - 按钮:点击下载方法
- (IBAction)startDownload:(UIButton *)sender {//如果点击下载按钮立即跳出下载完成,很可能试url有问题。此处找的是QQ音乐上的url,第二天便会更新//isDownloading:用于判断下载状态。进入下载时为YES,下载完成时为NOif (!isDownloading) {NSURL *url = [NSURL URLWithString:@"http://218.205.82.11/mobileoc.music.tc.qq.com/F000003BiKB44LknC0.flac?continfo=122542B50C9B1A0317535549A8AD143E8038EA7648884166&vkey=8078E72D66A07AD5C4B3696E52E8702B0612145B7EFF5BBB77FEAB63A1D6673DE51521C2D4397D8D659204309621EBB8EABA0FFD614A4171&guid=27238e629f2f6ffc850c749439bf81a1824fc41d&fromtag=55&uin=290363831"];//②:NSURLRequest
----------------NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];//通过http下载请求头来设置继续下载的位置NSString *value = [NSString stringWithFormat:@"bytes=%i-", (int)_receiveLength];[request setValue:value forHTTPHeaderField:@"Range"];//NSLog(@"继续下载:%@", request.allHTTPHeaderFields);//③:NSURLConnection
-------------------//发送异步网络请求_connection = [NSURLConnection connectionWithRequest:request delegate:self];//此时正在下载isDownloading = YES;//1.下面是不设置断点续传时的写法:在路径下直接创建单例文件用于保存下载到的数据//④:NSFileManager
-----------------// //创建空文件来保存下载数据
// NSString *fileName = @"听爸爸的话.mp3";
// _filePath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", fileName];
// //为路径创建单例
// [[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];//2.断点续传的写法//创建临时目录路径_filePath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/tmp/%@", @"听爸爸的话.mp3"];//NSLog(@"%@", _filePath);//判断临时文件是否存在,如果不存在则创建if (![[NSFileManager defaultManager] fileExistsAtPath:_filePath]) {//先创建临时文件夹NSString *dirPath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/tmp"];[[NSFileManager defaultManager] createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];//创建空的临时文件[[NSFileManager defaultManager] createFileAtPath:_filePath contents:nil attributes:nil];}}
}#pragma mark - 按钮:点击暂停下载方法
- (IBAction)cancelDownload:(UIButton *)sender {//取消下载[_connection cancel];//把数据包存入文件[self appendFileData:_totalData];//释放内存_totalData.data = nil;isDownloading = NO;//暂停时,保存已下载数据量和文件总数量到本地,保证程序退出后下次进入仍然可以实现断点续传[self saveUserDefaulst];
}#pragma mark - 文件写入方法
-(void) appendFileData:(NSData *) data {if (data.length == 0 || _filePath.length == 0) {//文件有误return;}//⑤:NSFileHandle
----------------NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:_filePath];[fileHandle seekToEndOfFile];[fileHandle writeData:data];[fileHandle closeFile];
}#pragma mark - NSURLConnectionDelegate
-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;//获取响应头NSDictionary *fields = httpResponse.allHeaderFields;//获取下载文件的总长度if (_totalLength == 0) {NSNumber *length = fields[@"Content-Length"];_totalLength = [length doubleValue];}//用于存放下载得到的数据,这是存放在内存中的_totalData = [[NSMutableData alloc] init];
}-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// double progress = _totalData.length / _totalLength;//根据当前下载的数据包大小来刷新进度条和Label的现实[_totalData appendData:data];_receiveLength += data.length;double progress = _receiveLength / _totalLength;//刷新UIself.progressLabel.text = [NSString stringWithFormat:@"%.2f%%", progress*100];self.progressLine.progress = progress;if (_totalData.length > 500*1024) {[self appendFileData: _totalData];//释放内存_totalData.data = nil;}
}-(void)connectionDidFinishLoading:(NSURLConnection *)connection {//方法1:该写法用于一次性完整下载(不推荐)
// //下载完成后保存数据到本地
// NSURL *url = connection.currentRequest.URL;
// NSString *urlString = url.absoluteString;
// NSString *fileName = urlString.lastPathComponent;
//
// //根据?分割符存放数据到数组
// NSArray *array = [fileName componentsSeparatedByString:@"?"];
// //获取文件名
// fileName = array[0];
//
// //获取沙盒路径
// NSString *filePath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", fileName];
// NSLog(@"%@", filePath);
//
// //把数据写入文件
// [_totalData writeToFile:filePath atomically:YES];//方法2:该方法满足了断点下载,虽然增大了开销但是确保了下载的安全性//文件末尾不足500KB大小的数据也要写入文件,所以文件下载“完成”后还要调用一个appendFileData方法[self appendFileData:_totalData];//清楚缓存_totalData.data = nil;self.progressLabel.text = @"下载完成";isDownloading = NO;//将下载完成的文件从临时目录剪切到目的目录NSFileManager *manager = [NSFileManager defaultManager];//确定目录文件路径NSString *targetFilePath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", @"听爸爸的话.mp3"];//判断目标文件是否存在,如果存在把旧文件删除,准备剪切操作if ([manager fileExistsAtPath:targetFilePath]) {[manager removeItemAtPath:targetFilePath error:nil];}//剪切方法的限制:不能把已经存在文件覆盖,如果此文件存在,在剪切之前需要先删除[manager moveItemAtPath:_filePath toPath:targetFilePath error:nil];//保存数据[self saveUserDefaulst];//下载完成把已下载数据量和文件总大小清零_receiveLength = 0;_totalLength = 0;
}#pragma mark - NSUserDefaults 保存下载的数据量和文件数据量到本地
-(void) saveUserDefaulst {//下载完成时,保存已下载数据量和文件宗数据量到本地NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];[userDefault setObject:@(_receiveLength) forKey:kReceivedTotal];[userDefault setObject:@(_totalLength) forKey:kTotal];//数据同步写入到plist文件[userDefault synchronize];
}
@end