NSURLConnection 文件下载的BUG及解决思路、方案
//
一般在文件名下载的过程中
,
应该告诉用户下载进度
(
进度条
).
思路: NSUrlConnection : 下载 .
{
小文件 : 直接利用 block 回调 ( 异步请求 , 下载好的文件就是 block 回调中的 data).
中文件 : 会造成内存暴涨 { 先将文件下载到内存中 (data), 然后再写入磁盘 }. 为了防止内存暴涨 , 不能直接使用 block 回调 .
大文件 : 会造成内存暴涨 { 先将文件下载到内存中 (data), 然后再写入磁盘 }. 为了防止内存暴涨 , 不能直接使用 block 回调 .
}
}
NSUrlConnection 下载 Bug 汇总 :
1. 异步回调下载
{
如果下载大文件 : 内存暴涨 . ( 下载下来的内容存储在 data 中 , 整个内容下载完毕之后 , 都存储在内存中 , 然后再一次性写入沙盒 .)
}
2. NSUrlConnectionDownloadDelegate
{
1. 可以监听下载进度 .
2. 找不到下载好的文件 .
}
3. NSUrlConnectionDataDelegate
{
1. 需要自己写业务逻辑监听下载进度 .
2. 将每次下载好的文件内容用一个属性保存起来 , 然后等文件都下载完毕之后再写入沙盒
{
1 > 内存暴涨 , 原因同异步回调下载 .
2 > 如果不及时清除属性中保存的内容 , 内存占用量会一直很大 .
}
}
4. 同时使用 NSUrlConnectionDownloadDelegate 和 NSUrlConnectionDataDelegate, 持续下载数据的方法只会调用 NSUrlConnectionDownloadDelegate. 所以不能同时使用这两个代理 .
5. NSUrlConnectionDataDelegate
{
1. 下载进度不能平滑的显示 ( 一段一段的显示 ): 默认下载回调是在主线程进行的 , 下载过程阻塞了主线程 ,UI 不能够及时的显示 .
2. 边下载 , 边写入沙盒
{
1. NSFileHandle
2. NSOutputStream
如果下载多次 , 都会造成本地下载好的文件变大 ...
}
}
6. NSUrlConnectionDataDelegate
{
1. 设置 代理回调在子线程 : 代理方法的调用确实在子线程 , 并且是多条线程 . 但是 , 主线程在执行 UI 操作的时候 , 后台线程会卡住 ( 会阻塞下载 ).
2. 开启新的下载任务之前 , 要检查本地文件和服务器文件的大小 . 做业务逻辑的判断 .
}
7. NSUrlConnectionDataDelegate
{
1. 将网络连接放在子线程 , 这样和主线程就没有任何关系 . 但是需要开启子线程运行循环之后 , 才能够执行 下载的代理方法 . : NSUrlConnectionDataDelegate 是一个特殊的运行循环 .( 下载完毕之后 , 运行循环自动停止 .) --> 保证在执行 UI 操作的时候 , 后台可以继续进行下载 . 设置代理回调为非主队列 , 可以在多条线程同时下载 .
2. 下载业务逻辑
{
1. 本地文件大小 > 服务器文件大小 : 1 > 删除本地文件 2 > 重新开始下载
2. 本地文件大小 < 服务器文件大小 :
{
* 1 如果本地文件大小 = 0 : 直接从 0 开始下载 ( 重新开始下载 ).
* 2 如果本地文件大小 > 0 : 断点续传 :
{
设置 Range 属性 , 告诉服务器断点续传开始的位置 .
Range 格式 : [bytes=%ld-%ld,X,Y] , 从 X 位置开始 , 下载 Y 个字节 .
设置 Range 属性之后 , 服务器返回的状态码会变成 206 .
}
}
}
}
思路: NSUrlConnection : 下载 .
{
小文件 : 直接利用 block 回调 ( 异步请求 , 下载好的文件就是 block 回调中的 data).
中文件 : 会造成内存暴涨 { 先将文件下载到内存中 (data), 然后再写入磁盘 }. 为了防止内存暴涨 , 不能直接使用 block 回调 .
大文件 : 会造成内存暴涨 { 先将文件下载到内存中 (data), 然后再写入磁盘 }. 为了防止内存暴涨 , 不能直接使用 block 回调 .
}
}
NSUrlConnection 下载 Bug 汇总 :
1. 异步回调下载
{
如果下载大文件 : 内存暴涨 . ( 下载下来的内容存储在 data 中 , 整个内容下载完毕之后 , 都存储在内存中 , 然后再一次性写入沙盒 .)
}
2. NSUrlConnectionDownloadDelegate
{
1. 可以监听下载进度 .
2. 找不到下载好的文件 .
}
3. NSUrlConnectionDataDelegate
{
1. 需要自己写业务逻辑监听下载进度 .
2. 将每次下载好的文件内容用一个属性保存起来 , 然后等文件都下载完毕之后再写入沙盒
{
1 > 内存暴涨 , 原因同异步回调下载 .
2 > 如果不及时清除属性中保存的内容 , 内存占用量会一直很大 .
}
}
4. 同时使用 NSUrlConnectionDownloadDelegate 和 NSUrlConnectionDataDelegate, 持续下载数据的方法只会调用 NSUrlConnectionDownloadDelegate. 所以不能同时使用这两个代理 .
5. NSUrlConnectionDataDelegate
{
1. 下载进度不能平滑的显示 ( 一段一段的显示 ): 默认下载回调是在主线程进行的 , 下载过程阻塞了主线程 ,UI 不能够及时的显示 .
2. 边下载 , 边写入沙盒
{
1. NSFileHandle
2. NSOutputStream
如果下载多次 , 都会造成本地下载好的文件变大 ...
}
}
6. NSUrlConnectionDataDelegate
{
1. 设置 代理回调在子线程 : 代理方法的调用确实在子线程 , 并且是多条线程 . 但是 , 主线程在执行 UI 操作的时候 , 后台线程会卡住 ( 会阻塞下载 ).
2. 开启新的下载任务之前 , 要检查本地文件和服务器文件的大小 . 做业务逻辑的判断 .
}
7. NSUrlConnectionDataDelegate
{
1. 将网络连接放在子线程 , 这样和主线程就没有任何关系 . 但是需要开启子线程运行循环之后 , 才能够执行 下载的代理方法 . : NSUrlConnectionDataDelegate 是一个特殊的运行循环 .( 下载完毕之后 , 运行循环自动停止 .) --> 保证在执行 UI 操作的时候 , 后台可以继续进行下载 . 设置代理回调为非主队列 , 可以在多条线程同时下载 .
2. 下载业务逻辑
{
1. 本地文件大小 > 服务器文件大小 : 1 > 删除本地文件 2 > 重新开始下载
2. 本地文件大小 < 服务器文件大小 :
{
* 1 如果本地文件大小 = 0 : 直接从 0 开始下载 ( 重新开始下载 ).
* 2 如果本地文件大小 > 0 : 断点续传 :
{
设置 Range 属性 , 告诉服务器断点续传开始的位置 .
Range 格式 : [bytes=%ld-%ld,X,Y] , 从 X 位置开始 , 下载 Y 个字节 .
设置 Range 属性之后 , 服务器返回的状态码会变成 206 .
}
}
}
}
具体的方案:
所有的网络请求只能是异步请求,。。。。同步请求(不会创建子线程)会阻塞主线程。。。。
小文件:直接利用block回调,把(data 保存到本地的路径就好了)下载好的文件就是block回调中的data 。小文件没问题。
NSString * string = @"http://192.168.1.254/xiaowenjian2.zip" ;
NSURL * url = [ NSURL URLWithString :string];
NSURLRequest * request =[ NSURLRequest requestWithURL :url];
// 发送异步请求,下载文件
[ NSURLConnection sendAsynchronousRequest :request queue :[ NSOperationQueue mainQueue ] completionHandler :^( NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
[data writeToFile : @"/Users/zzx/Desktop/xiaowenjian2.zip" atomically : YES ];
}];
中文件、大文件:BUG 1.会造成内存暴涨(先将文件下载到内存中,然后再写入磁盘) ,为了防止内存暴涨,不能直接使用block回调.——>解决方法,用NSURLConnectionDownloadDelegate。
- (
void
)touchesBegan:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
{
// NSURLConnectionDownloadDelegate
// 可以监听下载进度 .
// 但是 , 文件下载完毕之后 , 找不到下载好的文件在哪里 .(destinationURL 的位置没有文件 )
NSLog ( @"touchesBegan" );
// 文件下载 :
// http://127.0.0.1/xiaowenjian1.jpg : 网络接口地址 ( 文件下载地址 )
// http://127.0.0.1/xiaowenjian2.zip
// http://127.0.0.1/zhongwenjian.zip
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDownloadDelegate
// 频繁地从服务器下载资源 ( 文件 ). 知道文件下载完毕 .( 取消下载图片的操作 , 这一个方法是判断取消的具体位置 .)
// bytesWritten: 本次调用方法下载的数据量 .
// totalBytesWritten: 已经下载的总数据量 .
// expectedTotalBytes: : 需要下载的数据总量 ( 下载资源的大小 .)
- ( void )connection:( NSURLConnection *)connection didWriteData:( long long )bytesWritten totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
// 下载单位 : 字节
NSLog ( @" 本次下载的大小 :%lld 已经下载了 :%lld 总共需要下载 :%lld %@" ,bytesWritten,totalBytesWritten,expectedTotalBytes, [ NSThread currentThread ]);
}
- ( void )connectionDidResumeDownloading:( NSURLConnection *)connection totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
NSLog ( @" 断点续传的方法 , 不使用 .%@" ,[ NSThread currentThread ]);
}
- ( void )connectionDidFinishDownloading:( NSURLConnection *)connection destinationURL:( NSURL *) destinationURL
{
// destinationURL: 文件下载完毕之后 , 保存的地址 .
NSLog ( @" 文件下载完毕 ,%@ %@" ,destinationURL,[ NSThread currentThread ]);
}
{
// NSURLConnectionDownloadDelegate
// 可以监听下载进度 .
// 但是 , 文件下载完毕之后 , 找不到下载好的文件在哪里 .(destinationURL 的位置没有文件 )
NSLog ( @"touchesBegan" );
// 文件下载 :
// http://127.0.0.1/xiaowenjian1.jpg : 网络接口地址 ( 文件下载地址 )
// http://127.0.0.1/xiaowenjian2.zip
// http://127.0.0.1/zhongwenjian.zip
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDownloadDelegate
// 频繁地从服务器下载资源 ( 文件 ). 知道文件下载完毕 .( 取消下载图片的操作 , 这一个方法是判断取消的具体位置 .)
// bytesWritten: 本次调用方法下载的数据量 .
// totalBytesWritten: 已经下载的总数据量 .
// expectedTotalBytes: : 需要下载的数据总量 ( 下载资源的大小 .)
- ( void )connection:( NSURLConnection *)connection didWriteData:( long long )bytesWritten totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
// 下载单位 : 字节
NSLog ( @" 本次下载的大小 :%lld 已经下载了 :%lld 总共需要下载 :%lld %@" ,bytesWritten,totalBytesWritten,expectedTotalBytes, [ NSThread currentThread ]);
}
- ( void )connectionDidResumeDownloading:( NSURLConnection *)connection totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
NSLog ( @" 断点续传的方法 , 不使用 .%@" ,[ NSThread currentThread ]);
}
- ( void )connectionDidFinishDownloading:( NSURLConnection *)connection destinationURL:( NSURL *) destinationURL
{
// destinationURL: 文件下载完毕之后 , 保存的地址 .
NSLog ( @" 文件下载完毕 ,%@ %@" ,destinationURL,[ NSThread currentThread ]);
}
2.NSURLConnectionDownloadDelegate可以监听,但找不到tmp里面下载好的文件(解决方法:换代理方法用NSURLConnectionDataDelegate)
代理对象创建完毕后,代理方法会自动调用自动下载,不需要手动下载数据。
实体内容就是我们想要的数据,
响应头最先调用返回的是数据类型和大小,服务器信息
步骤:1.实例化一个NSMutableData,以保存下载好的数据
2。在 接收到数据(实体内容)拼接数据
3.在数据下载完毕后写入本地磁盘,用MD5校验两个文件时否一致,用终端,
#import
"ViewController.h"
@interface ViewController ()< NSURLConnectionDataDelegate , NSURLConnectionDownloadDelegate >
@property ( nonatomic , strong ) NSMutableData *fileData;
@end
@implementation ViewController
-( NSMutableData *)fileData
{
if (! _fileData ) {
_fileData = [ NSMutableData data ];
}
return _fileData ;
}
- ( void )viewDidLoad {
[ super viewDidLoad ];
// NSURLConnectionDataDelegate
// 直接用一个可变二进制数据文件接收下载好的数据 , 数据接收完毕之后 , 在本地保存 : 内存依然暴涨 . 文件写入本地磁盘之后 , 如果不及时释放下载好的文件 , 会造成内存一个很大 .
// NSURLConnectionDataDelegate : 检测下载进度 , 需要自己写业务逻辑 .
// NSURLConnectionDownloadDelegate 处理下载进度 , NSURLConnectionDataDelegate 处理下载数据 .
// NSURLConnectionDownloadDelegate 和 NSURLConnectionDataDelegate 方法 ( 持续下载数据的方法 ) 不可以同时调用 .
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
// 文件下载 :
// http://127.0.0.1/xiaowenjian1.jpg : 网络接口地址 ( 文件下载地址 )
// http://127.0.0.1/xiaowenjian2.zip
// http://127.0.0.1/zhongwenjian.zip
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 拼接下载好的数据
[ self . fileData appendData :data];
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 数据拼接完毕 , 保存在本地 .
[ self . fileData writeToFile : @"/Users/teacher/Desktop/111.zip" atomically : YES ];
self . fileData = nil ;
}
#pragma NSURLConnectionDownloadDelegate
- ( void )connection:( NSURLConnection *)connection didWriteData:( long long )bytesWritten totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
NSLog ( @"-------------------1111" );
}
- ( void )connectionDidFinishDownloading:( NSURLConnection *)connection destinationURL:( NSURL *) destinationURL
{
NSLog ( @"-------------%@" ,destinationURL);
}
@interface ViewController ()< NSURLConnectionDataDelegate , NSURLConnectionDownloadDelegate >
@property ( nonatomic , strong ) NSMutableData *fileData;
@end
@implementation ViewController
-( NSMutableData *)fileData
{
if (! _fileData ) {
_fileData = [ NSMutableData data ];
}
return _fileData ;
}
- ( void )viewDidLoad {
[ super viewDidLoad ];
// NSURLConnectionDataDelegate
// 直接用一个可变二进制数据文件接收下载好的数据 , 数据接收完毕之后 , 在本地保存 : 内存依然暴涨 . 文件写入本地磁盘之后 , 如果不及时释放下载好的文件 , 会造成内存一个很大 .
// NSURLConnectionDataDelegate : 检测下载进度 , 需要自己写业务逻辑 .
// NSURLConnectionDownloadDelegate 处理下载进度 , NSURLConnectionDataDelegate 处理下载数据 .
// NSURLConnectionDownloadDelegate 和 NSURLConnectionDataDelegate 方法 ( 持续下载数据的方法 ) 不可以同时调用 .
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
// 文件下载 :
// http://127.0.0.1/xiaowenjian1.jpg : 网络接口地址 ( 文件下载地址 )
// http://127.0.0.1/xiaowenjian2.zip
// http://127.0.0.1/zhongwenjian.zip
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 拼接下载好的数据
[ self . fileData appendData :data];
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 数据拼接完毕 , 保存在本地 .
[ self . fileData writeToFile : @"/Users/teacher/Desktop/111.zip" atomically : YES ];
self . fileData = nil ;
}
#pragma NSURLConnectionDownloadDelegate
- ( void )connection:( NSURLConnection *)connection didWriteData:( long long )bytesWritten totalBytesWritten:( long long )totalBytesWritten expectedTotalBytes:( long long ) expectedTotalBytes
{
NSLog ( @"-------------------1111" );
}
- ( void )connectionDidFinishDownloading:( NSURLConnection *)connection destinationURL:( NSURL *) destinationURL
{
NSLog ( @"-------------%@" ,destinationURL);
}
@end
!!!!内存还是暴涨!监听下载进度条! 需要自己写业务逻辑
内存还是暴涨的解决方法:数据追加(
边下载边保存
(
写入本地
).
还要保证后续下载的数据追加在之前下载数据的后面
.)
第一种方法:数据追加:
1.NSFileHandle:文件操作句柄,用来操作文件内部——>造成的BUG,如果多次下载,会造成下载完毕后的文件变大,需要做业务逻辑
2.NSFileManager:用来操作文件(获取文件信息/删除/移动/复制。。。)
#import
"ViewController.h"
@interface ViewController ()< NSURLConnectionDataDelegate >
// 文件下载完毕之后 , 保存的路径 .
@property ( nonatomic , copy ) NSString *filePath;
@end
@implementation ViewController
- ( void )viewDidLoad {
[ super viewDidLoad ];
// 1. 下载过程中 , 内存不能变大 .
// 边下载边保存 ( 写入本地 ). 还要保证后续下载的数据追加在之前下载数据的后面 .
// 数据追加 :
// 1. NSFileHandle: 文件操作句柄 , 用来操作文件内部
// NSFileManager: 用来操纵文件 ( 获得文件信息 / 删除 / 移动 / 复制 ...)
// 如果多次下载 , 会造成下载完毕的文件变大 ...(2/3/4/5 倍增加 .), 需要做业务逻辑处理 .
// 创建文件句柄
// 根据文件路径 , 实例化文件操作句柄 ( 写入 )
// 如果传入的路径不存在 , 文件句柄会实例化失败 . nil.
// 如果传入的文件路径存在 , 文件句柄会实例化成功 , 并且指向这个需要操作的文件 .
NSFileHandle *handle = [ NSFileHandle fileHandleForWritingAtPath : @"" ];
// 2. 监听下载进度 .
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 实例化文件句柄 , 操纵文件
NSFileHandle *handle = [ NSFileHandle fileHandleForWritingAtPath : self . filePath ];
if (handle) {
// 往文件后面追加文件内容 .
// 1. 将文件句柄移动到文件最后 ( 末尾 )
[handle seekToEndOfFile ];
// 2. 追加文件
[handle writeData :data];
[handle closeFile ];
} else
{
// 第一次实例化路径 ( 创建文件 )
// 如果这个路径下有文件了 , 会自动覆盖 ; 如果没有文件 , 会创建一个文件 .
[data writeToFile : self . filePath atomically : YES ];
}
@interface ViewController ()< NSURLConnectionDataDelegate >
// 文件下载完毕之后 , 保存的路径 .
@property ( nonatomic , copy ) NSString *filePath;
@end
@implementation ViewController
- ( void )viewDidLoad {
[ super viewDidLoad ];
// 1. 下载过程中 , 内存不能变大 .
// 边下载边保存 ( 写入本地 ). 还要保证后续下载的数据追加在之前下载数据的后面 .
// 数据追加 :
// 1. NSFileHandle: 文件操作句柄 , 用来操作文件内部
// NSFileManager: 用来操纵文件 ( 获得文件信息 / 删除 / 移动 / 复制 ...)
// 如果多次下载 , 会造成下载完毕的文件变大 ...(2/3/4/5 倍增加 .), 需要做业务逻辑处理 .
// 创建文件句柄
// 根据文件路径 , 实例化文件操作句柄 ( 写入 )
// 如果传入的路径不存在 , 文件句柄会实例化失败 . nil.
// 如果传入的文件路径存在 , 文件句柄会实例化成功 , 并且指向这个需要操作的文件 .
NSFileHandle *handle = [ NSFileHandle fileHandleForWritingAtPath : @"" ];
// 2. 监听下载进度 .
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 实例化文件句柄 , 操纵文件
NSFileHandle *handle = [ NSFileHandle fileHandleForWritingAtPath : self . filePath ];
if (handle) {
// 往文件后面追加文件内容 .
// 1. 将文件句柄移动到文件最后 ( 末尾 )
[handle seekToEndOfFile ];
// 2. 追加文件
[handle writeData :data];
[handle closeFile ];
} else
{
// 第一次实例化路径 ( 创建文件 )
// 如果这个路径下有文件了 , 会自动覆盖 ; 如果没有文件 , 会创建一个文件 .
[data writeToFile : self . filePath atomically : YES ];
}
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
}
@end
第二种方法:文件的数据流/输入输出流:NSOutputStream—>负责建立一个“管道”,让数据流顺着这个“管道”流入指定的文件——>造成的BUG,如果多次下载,会造成下载完毕后的文件变大,需要做业务逻辑。
//
实例化对象
// 如果这个文件路径不存在,会自动创建一个空文件.如果文件存在,就直接在文件后面追加文件.
// NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];
NSOutputStream稳定性不如NSFileHandle
步骤:注意,管道式需要手动开启的 open
#import "ViewController.h"
@interface ViewController ()< NSURLConnectionDataDelegate >
// 文件下载完毕之后 , 保存的路径 .
@property ( nonatomic , copy ) NSString *filePath;
// 文件输入输出流管道 .
@property ( nonatomic , strong ) NSOutputStream *stream;
@end
@implementation
ViewController
- (
void
)touchesBegan:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event
{
NSLog ( @"touchesBegan" );
// http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg
NSString *urlString = @"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
// 创建管道 .
self . stream = [[ NSOutputStream alloc ] initToFileAtPath : self . filePath append : YES ];
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
// NSLog(@"%@",data);
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道 .
[ self . stream close ];
{
NSLog ( @"touchesBegan" );
// http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg
NSString *urlString = @"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
// 创建管道 .
self . stream = [[ NSOutputStream alloc ] initToFileAtPath : self . filePath append : YES ];
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
// NSLog(@"%@",data);
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道 .
[ self . stream close ];
}
(
显示问题 )监听下载进度条的解决方法:
1.需要我那件总大小(定义一个属性 =respond。expectedContentLenght),当前下载 的数据量(定义一个属性 = data。length)——>BUG:直接设置进度条,进度条不能平滑是显示下载进度
#import
"ViewController.h"
@interface ViewController ()< NSURLConnectionDataDelegate >
// 文件下载完毕之后 , 保存的路径 .
@property ( nonatomic , copy ) NSString *filePath;
// 文件输入输出流管道 .
@property ( nonatomic , strong ) NSOutputStream *stream;
// 文件总大小
@property ( nonatomic , assign ) long long expectedLength;
// 当前已经下载的数据量
@property ( nonatomic , assign ) long long totalBytes;
// 下载进度条
@property ( nonatomic , strong ) UIProgressView *progressView;
@end
@implementation ViewController
-( UIProgressView *)progressView
{
if (! _progressView ) {
_progressView = [[ UIProgressView alloc ] initWithFrame : CGRectMake ( 20 , 50 , 335 , 2 )];
// 进度条颜色
_progressView . tintColor = [ UIColor greenColor ];
[ self . view addSubview : _progressView ];
}
return _progressView ;
}
- ( void )viewDidLoad {
[ super viewDidLoad ];
// 1. 下载过程中 , 内存不能变大 .
// 2. 监听下载进度 .
// 1. 文件总大小 , 当前下载的数据量 .
// 直接设置进度条 , 进度条不能平滑显示下载进度 ( 为什么 ?)
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
// http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg
//
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 记录文件总大小
self . expectedLength = response. expectedContentLength ;
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
// 创建管道 .
self . stream = [[ NSOutputStream alloc ] initToFileAtPath : self . filePath append : YES ];
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
// NSLog(@" 本次接收到 %ld 的数据 %@",data.length,[NSThread currentThread]);
// 记录已经下载的文件大小 .
self . totalBytes += data. length ;
NSLog ( @"%lld %lld %@" , self . totalBytes , self . expectedLength ,[ NSThread currentThread ]);
self . progressView . progress = ( float ) self . totalBytes / self . expectedLength ;
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
// NSLog(@"%@",data);
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道 .
[ self . stream close ];
}
@interface ViewController ()< NSURLConnectionDataDelegate >
// 文件下载完毕之后 , 保存的路径 .
@property ( nonatomic , copy ) NSString *filePath;
// 文件输入输出流管道 .
@property ( nonatomic , strong ) NSOutputStream *stream;
// 文件总大小
@property ( nonatomic , assign ) long long expectedLength;
// 当前已经下载的数据量
@property ( nonatomic , assign ) long long totalBytes;
// 下载进度条
@property ( nonatomic , strong ) UIProgressView *progressView;
@end
@implementation ViewController
-( UIProgressView *)progressView
{
if (! _progressView ) {
_progressView = [[ UIProgressView alloc ] initWithFrame : CGRectMake ( 20 , 50 , 335 , 2 )];
// 进度条颜色
_progressView . tintColor = [ UIColor greenColor ];
[ self . view addSubview : _progressView ];
}
return _progressView ;
}
- ( void )viewDidLoad {
[ super viewDidLoad ];
// 1. 下载过程中 , 内存不能变大 .
// 2. 监听下载进度 .
// 1. 文件总大小 , 当前下载的数据量 .
// 直接设置进度条 , 进度条不能平滑显示下载进度 ( 为什么 ?)
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event
{
NSLog ( @"touchesBegan" );
// http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.4.dmg
//
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
}
#pragma NSURLConnectionDataDelegate
// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response
{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 记录文件总大小
self . expectedLength = response. expectedContentLength ;
// 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/teacher/Desktop/%@" ,response. suggestedFilename ];
// 创建管道 .
self . stream = [[ NSOutputStream alloc ] initToFileAtPath : self . filePath append : YES ];
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data
{
// NSLog(@" 本次接收到 %ld 的数据 %@",data.length,[NSThread currentThread]);
// 记录已经下载的文件大小 .
self . totalBytes += data. length ;
NSLog ( @"%lld %lld %@" , self . totalBytes , self . expectedLength ,[ NSThread currentThread ]);
self . progressView . progress = ( float ) self . totalBytes / self . expectedLength ;
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
// NSLog(@"%@",data);
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道 .
[ self . stream close ];
}
@end
解决
BUG:直接设置进度条,进度条不能平滑是显示下载进度
下载和刷新进度条都在主线程中,所以把下载进度条放在子线程中操作。优先保证主线程的运行———>还是有坑,解决思路————>关于网络连接放到子线程中处理(还是有坑,因为NSURLConnectDelegate是一个特殊的事件源,
要手动开启运行循环,CFRunLoopRun();先添加事件源,再开启运行循环
)
- (
void
)touchesBegan:(
NSSet
<
UITouch
*> *)touches withEvent:(
UIEvent
*)event{
//
// 1. 下载过程中 , 内存不能变大 .
// 2. 监听下载进度 .
// 1. 文件总大小 , 当前下载的数据量 .
// 直接设置进度条 , 进度条不能平滑显示下载进度 ( 为什么 ?)
// 把网络连接添加到子线程中,就等于把代理添加到了子线程中。 NSUrlConnectionDelegate 是一个特殊的事件源 . 代理方法想要执行 , 必须运行循环来执行 . 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^{
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 将代理回调设置子线程。相当于 NSDefaltModes 模式将定时器添加在线程中 . 优先保证主线程的运行 .
[conn setDelegateQueue :[[ NSOperationQueue alloc ] init ]];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
// NSUrlConnectionDelegate 是一个特殊的事件源 . 代理方法想要执行 , 必须运行循环来执行 .
// 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
CFRunLoopRun ();
// [[NSRunLoop currentRunLoop] run];
});
}
#pragma mark - NSURLConnectionDataDelegate
//// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response{
// 记录文件总大小
self . expectedLength = response. expectedContentLength ;
NSLog ( @" 响应头信息 :%@---%@" ,response,[ NSThread currentThread ]);
// // 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/zzx/Desktop/%@" ,response. suggestedFilename ];
// 实例化 ” 管道 “
self . stream = [ NSOutputStream outputStreamToFileAtPath : self . filePath append : YES ];
// 开启管道
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data{
// NSLog(@"%@ ---%@",[NSThread currentThread],data);
// 每次调用,都把数据保存到定义的数据属性中
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 记录已经下载的文件大小 .
self . totalBytes += data. length ;
dispatch_async ( dispatch_get_main_queue (), ^{
// 进度条进度
self . progress . progress = ( float ) self . totalBytes / self . expectedLength ;
});
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道
[ self . stream close ];
}
//
// 1. 下载过程中 , 内存不能变大 .
// 2. 监听下载进度 .
// 1. 文件总大小 , 当前下载的数据量 .
// 直接设置进度条 , 进度条不能平滑显示下载进度 ( 为什么 ?)
// 把网络连接添加到子线程中,就等于把代理添加到了子线程中。 NSUrlConnectionDelegate 是一个特殊的事件源 . 代理方法想要执行 , 必须运行循环来执行 . 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^{
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSURLRequest *request = [ NSURLRequest requestWithURL :url];
// 下载 , 代理 .
// 创建网络连接 , 设置代理对象
NSURLConnection *conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 将代理回调设置子线程。相当于 NSDefaltModes 模式将定时器添加在线程中 . 优先保证主线程的运行 .
[conn setDelegateQueue :[[ NSOperationQueue alloc ] init ]];
// 代理对象创建完毕之后 , 就会自动调用代理方法 . 不需要手动启动的 .
[conn start ];
// NSUrlConnectionDelegate 是一个特殊的事件源 . 代理方法想要执行 , 必须运行循环来执行 .
// 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
CFRunLoopRun ();
// [[NSRunLoop currentRunLoop] run];
});
}
#pragma mark - NSURLConnectionDataDelegate
//// 接收到 响应头信息的时候就会调用 .( 最先调用的方法 .), 只会调用一次 .
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response{
// 记录文件总大小
self . expectedLength = response. expectedContentLength ;
NSLog ( @" 响应头信息 :%@---%@" ,response,[ NSThread currentThread ]);
// // 准备下载文件数据之前 , 实例化文件下载路径 .
self . filePath = [ NSString stringWithFormat : @"/Users/zzx/Desktop/%@" ,response. suggestedFilename ];
// 实例化 ” 管道 “
self . stream = [ NSOutputStream outputStreamToFileAtPath : self . filePath append : YES ];
// 开启管道
// 管道是需要手动开启的 .
[ self . stream open ];
}
// 接收到 数据 ( 实体内容 ) 的时候就会调用 . 也会调用多次 .
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data{
// NSLog(@"%@ ---%@",[NSThread currentThread],data);
// 每次调用,都把数据保存到定义的数据属性中
NSLog ( @" 本次接收到 %ld 的数据 %@" ,data. length ,[ NSThread currentThread ]);
// 记录已经下载的文件大小 .
self . totalBytes += data. length ;
dispatch_async ( dispatch_get_main_queue (), ^{
// 进度条进度
self . progress . progress = ( float ) self . totalBytes / self . expectedLength ;
});
// 顺着管道 , 流入数据 .
[ self . stream write :[data bytes ] maxLength :data. length ];
}
// 网络完成之后 ( 数据下载完毕 ), 就会调用 .
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection
{
NSLog ( @" 下载完毕 ...%@" ,[ NSThread currentThread ]);
// 关闭管道
[ self . stream close ];
}
3.BUG——>下载过程中,下载到本地的文件不能变大。
下载业务逻辑
{
1. 本地文件大小 > 服务器文件大小 : 1 > 删除本地文件 2 > 重新开始下载
2. 本地文件大小 < 服务器文件大小 :
{
* 1 如果本地文件大小 = 0 : 直接从 0 开始下载 ( 重新开始下载 ).
* 2 如果本地文件大小 > 0 : 断点续传 :
{
设置 Range 属性 , 告诉服务器断点续传开始的位置 .
{
1. 本地文件大小 > 服务器文件大小 : 1 > 删除本地文件 2 > 重新开始下载
2. 本地文件大小 < 服务器文件大小 :
{
* 1 如果本地文件大小 = 0 : 直接从 0 开始下载 ( 重新开始下载 ).
* 2 如果本地文件大小 > 0 : 断点续传 :
{
设置 Range 属性 , 告诉服务器断点续传开始的位置 .
Range
格式
:
bytes=x-y 从 x 字节开始下载,下载 y 个字节
bytes=x- 从 x 字节开始下载,下载到文件末尾
bytes=-x
从
文件开始下载,下载
x
字节
设置 Range 属性之后,服务器返回的状态码会变成 206 .
#import "ViewController.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
// 定义一个数据保存到本地的地址属性
@property ( nonatomic , copy ) NSString * filePath;
// 文件输入输出流
@property ( nonatomic , strong ) NSOutputStream * steam;
// 下载进度条
@property ( nonatomic , strong ) UIProgressView * progressView;
// 已经下载的数据量
@property ( nonatomic , assign ) long long totalBtyes;
// 文件总数据量
@property ( nonatomic , assign ) long long expectedBtyesLength;
// 本地文件大小
@property ( nonatomic , assign ) long long localFileLength;
@end
@implementation ViewController
-( UIProgressView *)progressView{
if (! _progressView ) {
_progressView = [[ UIProgressView alloc ] initWithFrame : CGRectMake ( 20 , 50 , 335 , 2 )];
_progressView . tintColor = [ UIColor redColor ];
[ self . view addSubview : _progressView ];
}
return _progressView ;
}
- ( void )viewDidLoad {
[ super viewDidLoad ];
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
self . filePath = @"/Users/zzx/Desktop/dawenjian" ;
// 1. 先检查服务器文件大小 .
[ self checkServerFileWithUrlString :urlString];
// 2. 检查本地文件大小
[ self getFilepathWithUrlString : self . filePath ];
// 主线程中执行 UI 操作
self . progressView . progress = ( float ) self . totalBtyes / self . expectedBtyesLength ;
}
- ( void )touchesBegan:( NSSet < UITouch *> *)touches withEvent:( UIEvent *)event{
NSLog ( @"touchesBegan" );
NSString *urlString = @"http://127.0.0.1/dawenjian.zip" ;
// 1. 先检查服务器文件大小 .
[ self checkServerFileWithUrlString :urlString];
// 2. 检查本地文件大小
[ self getFilepathWithUrlString : self . filePath ];
if ( self . localFileLength >= self . expectedBtyesLength ) { // 删除本地文件 , 并且重新开始下载
NSLog ( @" 删除本地文件 , 开始新文件的下载 " );
// 1. 删除本地文件
[[ NSFileManager defaultManager ] removeItemAtPath : self . filePath error : NULL ];
// 2. 重新开始下载
[ self getFilepathWithUrlString :urlString];
return ;
}
if ( self . localFileLength < self . expectedBtyesLength ) { // 本地保存的文件小于服务器的文件
if ( self . localFileLength > 0 ) {
NSLog ( @" 断点续传 " );
} else
{
NSLog ( @" 重新开始下载 " );
}
// 断点续传 ( 包含了两个过程 : 1. 从 0 开始下载 2. 从断点开始下载 )
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^{
// 1. 创建请求
NSURL *url = [ NSURL URLWithString :urlString];
NSMutableURLRequest *request = [ NSMutableURLRequest requestWithURL :url];
// 从 X 位置 开始 ,下载到 文件末尾
NSString * range = [ NSString stringWithFormat : @"bytes=%lld-" , self . localFileLength ];
// 告诉服务器,从哪里开始下载
[request setValue :range forHTTPHeaderField : @"Range" ];
// 创建网络连接。设置代理对象
NSURLConnection * conn = [[ NSURLConnection alloc ] initWithRequest :request delegate : self ];
// 将代理回调设置子线程 . 相当于 NSDefaltModes 模式将定时器添加在线程中 . 优先保证主线程的运行 .
[conn setDelegateQueue :[[ NSOperationQueue alloc ] init ]];
[conn start ];
// 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
CFRunLoopRun ();
});
}
}
#pragma mark - 检查服务器文件大小 ( 同步 还是 异步 ?) -- 同步请求 .
- ( void )checkServerFileWithUrlString:( NSString *)urlString{
// 只获取 response , 不要 data.
NSURL *url = [ NSURL URLWithString :urlString];
NSMutableURLRequest *request = [ NSMutableURLRequest requestWithURL :url];
// 设置请求方法 :
// HEAD 是 http 请求规定的方法 . file 协议没有这个方法 .
// 这个方法只获得响应头信息 , 不会获取具体的文件内容 . 速度比较快 , 一般是使用同步请求发送的 .
request. HTTPMethod = @"HEAD" ;
NSURLResponse * response = nil ;
[ NSURLConnection sendSynchronousRequest :request returningResponse :&response error : NULL ];
NSLog ( @"%lld" ,response. expectedContentLength );
self . expectedBtyesLength = response. expectedContentLength ;
}
#pragma mark - 发送本地请求,获知本地文件大小
-( void )getFilepathWithUrlString:( NSString *) urlString{
// 利用 NSFileManager 来检查本地文件大小 .
// 获取文件管理器对象
// 检查这个路径下 , 是否有这个文件 .
BOOL is_YES = [[ NSFileManager defaultManager ] fileExistsAtPath : self . filePath ];
if (is_YES) {
NSLog ( @" 本地文件存在 " );
// 获取本地文件信息 , 文件信息中不包含文件类型 .
NSDictionary * dict = [[ NSFileManager defaultManager ] attributesOfItemAtPath : self . filePath error : NULL ];
// 直接通过字典属性取值 , 得不到数字 ( 得到是对象 ), 没法对比 .
NSLog ( @"%@" ,dict);
// 记录本地文件的大小
self . localFileLength = [dict[ NSFileSize ] integerValue ];
} else {
self . localFileLength = 0 ;
NSLog ( @" 本地文件不存在 " );
}
}
#pragma mark -NSURLConnectionDataDelegate
// 接收响应头信息时会调用,只调用一次
- ( void )connection:( NSURLConnection *)connection didReceiveResponse:( NSURLResponse *)response{
NSLog ( @"%@ %@" ,response, [ NSThread currentThread ]);
// 获取总数据量
// self.expectedBtyesLength = response.expectedContentLength;
// 实例化接收数据的地址 地址名称随机最优
// self.filePath = [NSString stringWithFormat:@"/Users/zzx/Desktop/%@",response.suggestedFilename];
// 实例化管道
self . steam = [ NSOutputStream outputStreamToFileAtPath : self . filePath append : YES ];
// 打开管道
[ self . steam open ];
}
// 发送网络请求 , 下载数据 .
- ( void )getServerDataWithUrlString:( NSString *)urlString{
// 异步网络请求
dispatch_async ( dispatch_get_global_queue ( 0 , 0 ), ^{
NSURL * url =[ NSURL URLWithString :urlString];
NSURLRequest * request = [ NSURLRequest requestWithURL :url];
// 设置请求代理
NSURLConnection * conn = [ NSURLConnection connectionWithRequest :request delegate : self ];
// 对代理回调在子线程中执行
[conn setDelegateQueue :[[ NSOperationQueue alloc ] init ]];
// 开启代理方法 ,也会自动开启
[conn start ];
// 手动开启运行循环 : 先添加事件源 , 再开启运行循环 .
CFRunLoopRun ();
});
}
// 接收数据时会调用,持续调用,直到完成
- ( void )connection:( NSURLConnection *)connection didReceiveData:( NSData *)data{
// 记录已经下载的文件大小 .
self . totalBtyes += data. length ;
NSLog ( @"%lld %lld %@" , self . localFileLength , self . expectedBtyesLength ,[ NSThread currentThread ]);
dispatch_async ( dispatch_get_main_queue (), ^{
self . progressView . progress = ( float ) self . totalBtyes / self . expectedBtyesLength ;
});
// 顺着管道 , 流入数据 .
[ self . steam write :[data bytes ] maxLength :data. length ];
}
// 下载结束后调用,
- ( void )connectionDidFinishLoading:( NSURLConnection *)connection{
// 关闭管道
[ self . steam close ];
}
@end
大文件:
会造成内存暴涨
{
先将文件下载到内存中
(data),
然后再写入磁盘
}.
为了防止内存暴涨
,
不能直接使用
block
回调
.