下面是源码,注释掉的部分代码是一次性下载的功能(不推荐),正常运行的代码实现了:
- 功能:根据提供的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,下载完成时为NO
if (!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;
//刷新UI
self.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