NSURLConnection 文件下载的BUG及解决思路、方案

来源:互联网 发布:电脑锣编程代码 编辑:程序博客网 时间:2024/05/17 04:18
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 .
            }
        }
       
    }
}



具体的方案:

所有的网络请求只能是异步请求,。。。。同步请求(不会创建子线程)会阻塞主线程。。。。

小文件:直接利用block回调,把(data 保存到本地的路径就好了)下载好的文件就是block回调中的data 。小文件没问题。
  
   
NSString * string = @"http://192.168.1.254/xiaowenjian2.zip";
   
   
NSURL * url = [NSURLURLWithString:string];
   
   
NSURLRequest * request =[ NSURLRequest requestWithURL:url];
   
//发送异步请求,下载文件
    [
NSURLConnectionsendAsynchronousRequest:requestqueue:[NSOperationQueuemainQueue]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 = [NSURLURLWithString:urlString];
   
   
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
   
   
// 下载,代理.
   
   
// 创建网络连接,设置代理对象
   
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
   
   
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
    [conn
start];
   
   
}

#pragma NSURLConnectionDownloadDelegate
//频繁地从服务器下载资源(文件).知道文件下载完毕.(取消下载图片的操作,这一个方法是判断取消的具体位置.)
// bytesWritten:本次调用方法下载的数据量.
// totalBytesWritten:已经下载的总数据量.
// expectedTotalBytes: :需要下载的数据总量(下载资源的大小.)
- (
void)connection:(NSURLConnection*)connection didWriteData:(longlong)bytesWritten totalBytesWritten:(longlong)totalBytesWritten expectedTotalBytes:(longlong) expectedTotalBytes
{
   
// 下载单位:字节
   
NSLog(@"本次下载的大小:%lld已经下载了:%lld总共需要下载:%lld %@",bytesWritten,totalBytesWritten,expectedTotalBytes, [NSThreadcurrentThread]);
}

- (
void)connectionDidResumeDownloading:(NSURLConnection*)connection totalBytesWritten:(longlong)totalBytesWritten expectedTotalBytes:(longlong) expectedTotalBytes
{
   
NSLog(@"断点续传的方法,不使用.%@",[NSThreadcurrentThread]);
}

- (
void)connectionDidFinishDownloading:(NSURLConnection*)connection destinationURL:(NSURL*) destinationURL
{
   
// destinationURL:文件下载完毕之后,保存的地址.
   
NSLog(@"文件下载完毕,%@ %@",destinationURL,[NSThreadcurrentThread]);
}


  2.NSURLConnectionDownloadDelegate可以监听,但找不到tmp里面下载好的文件(解决方法:换代理方法用NSURLConnectionDataDelegate)

代理对象创建完毕后,代理方法会自动调用自动下载,不需要手动下载数据。

实体内容就是我们想要的数据,
响应头最先调用返回的是数据类型和大小,服务器信息

步骤:1.实例化一个NSMutableData,以保存下载好的数据
2。在 接收到数据(实体内容)拼接数据
3.在数据下载完毕后写入本地磁盘,用MD5校验两个文件时否一致,用终端,

#import"ViewController.h"

@interfaceViewController ()<NSURLConnectionDataDelegate,NSURLConnectionDownloadDelegate>

@property(nonatomic,strong)NSMutableData*fileData;

@end

@implementationViewController

-(
NSMutableData*)fileData
{
   
if (!_fileData) {
       
_fileData = [NSMutableDatadata];
    }
   
return _fileData;
}

- (
void)viewDidLoad {
    [
superviewDidLoad];
   
   
// 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 = [NSURLURLWithString:urlString];
   
   
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
   
   
// 下载,代理.
   
   
// 创建网络连接,设置代理对象
   
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
   
   
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
    [conn
start];
   
}


#pragma NSURLConnectionDataDelegate
//接收到 响应头信息的时候就会调用.(最先调用的方法.),只会调用一次.
- (
void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
   
NSLog(@"%@ %@",response, [NSThreadcurrentThread]);
}


//接收到 数据(实体内容)的时候就会调用.也会调用多次.
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
   
NSLog(@"本次接收到%ld 的数据 %@",data.length,[NSThreadcurrentThread]);
   
// 拼接下载好的数据
    [
self.fileDataappendData:data];
   
}

//网络完成之后(数据下载完毕),就会调用.
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection
{
   
NSLog(@"下载完毕...%@",[NSThreadcurrentThread]);
   
   
// 数据拼接完毕,保存在本地.
    [
self.fileDatawriteToFile:@"/Users/teacher/Desktop/111.zip"atomically:YES];
   
   
self.fileData= nil;
}


#pragma NSURLConnectionDownloadDelegate

- (
void)connection:(NSURLConnection*)connection didWriteData:(longlong)bytesWritten totalBytesWritten:(longlong)totalBytesWritten expectedTotalBytes:(longlong) expectedTotalBytes
{
   
NSLog(@"-------------------1111");
}


- (
void)connectionDidFinishDownloading:(NSURLConnection*)connection destinationURL:(NSURL*) destinationURL
{
   
NSLog(@"-------------%@",destinationURL);
}



@end

!!!!内存还是暴涨!监听下载进度条!  需要自己写业务逻辑
内存还是暴涨的解决方法:数据追加边下载边保存(写入本地).还要保证后续下载的数据追加在之前下载数据的后面.
第一种方法:数据追加:
1.NSFileHandle:文件操作句柄,用来操作文件内部——>造成的BUG,如果多次下载,会造成下载完毕后的文件变大,需要做业务逻辑
2.NSFileManager:用来操作文件(获取文件信息/删除/移动/复制。。。)
#import"ViewController.h"

@interfaceViewController ()<NSURLConnectionDataDelegate>

//文件下载完毕之后,保存的路径.
@property(nonatomic,copy)NSString*filePath;

@end

@implementationViewController


- (
void)viewDidLoad {
    [
superviewDidLoad];

   
// 1. 下载过程中,内存不能变大.
   
   
// 边下载边保存(写入本地).还要保证后续下载的数据追加在之前下载数据的后面.
   
// 数据追加:
   
// 1. NSFileHandle: 文件操作句柄,用来操作文件内部
   
// NSFileManager:用来操纵文件(获得文件信息/删除/移动/复制...)
   
// 如果多次下载,会造成下载完毕的文件变大...(2/3/4/5倍增加.),需要做业务逻辑处理.
   
   
// 创建文件句柄
   
// 根据文件路径,实例化文件操作句柄(写入)
   
// 如果传入的路径不存在,文件句柄会实例化失败. nil.
   
// 如果传入的文件路径存在,文件句柄会实例化成功,并且指向这个需要操作的文件.
   
NSFileHandle *handle = [NSFileHandlefileHandleForWritingAtPath:@""];
   
   
   
   
// 2.监听下载进度.
   
}

- (
void)touchesBegan:(NSSet<UITouch*> *)touches withEvent:(UIEvent*)event
{
   
NSLog(@"touchesBegan");
   
   
NSString *urlString = @"http://127.0.0.1/dawenjian.zip";
   
   
// 1. 创建请求
   
NSURL *url = [NSURLURLWithString:urlString];
   
   
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
   
   
// 下载,代理.
   
   
// 创建网络连接,设置代理对象
   
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
   
   
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
    [conn
start];
   
}


#pragma NSURLConnectionDataDelegate
//接收到 响应头信息的时候就会调用.(最先调用的方法.),只会调用一次.
- (
void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
   
NSLog(@"%@ %@",response, [NSThreadcurrentThread]);
   
   
   
// 准备下载文件数据之前,实例化文件下载路径.
   
self.filePath= [NSStringstringWithFormat:@"/Users/teacher/Desktop/%@",response.suggestedFilename];
   
}


//接收到 数据(实体内容)的时候就会调用.也会调用多次.
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
   
NSLog(@"本次接收到%ld 的数据 %@",data.length,[NSThreadcurrentThread]);

   
// 实例化文件句柄,操纵文件
   
NSFileHandle *handle = [NSFileHandlefileHandleForWritingAtPath:self.filePath];
   
   
if (handle) {
       
       
// 往文件后面追加文件内容.
       
       
// 1. 将文件句柄移动到文件最后(末尾)
        [handle
seekToEndOfFile];
       
       
// 2. 追加文件
        [handle
writeData:data];
       
        [handle
closeFile];
       
    }
else
    {
       
// 第一次实例化路径(创建文件)
       
// 如果这个路径下有文件了,会自动覆盖;如果没有文件,会创建一个文件.
        [data
writeToFile:self.filePathatomically:YES];
    }
  
}

//网络完成之后(数据下载完毕),就会调用.
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection
{
   
NSLog(@"下载完毕...%@",[NSThreadcurrentThread]);
}

@end



第二种方法:文件的数据流/输入输出流:NSOutputStream—>负责建立一个“管道”,让数据流顺着这个“管道”流入指定的文件——>造成的BUG,如果多次下载,会造成下载完毕后的文件变大,需要做业务逻辑。
 //实例化对象
    // 如果这个文件路径不存在,会自动创建一个空文件.如果文件存在,就直接在文件后面追加文件. // NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];

NSOutputStream稳定性不如NSFileHandle

步骤:注意,管道式需要手动开启的 open

#import"ViewController.h"

@interfaceViewController ()<NSURLConnectionDataDelegate>

//文件下载完毕之后,保存的路径.
@property(nonatomic,copy)NSString*filePath;

//文件输入输出流管道.
@property(nonatomic,strong)NSOutputStream*stream;

@end

@implementationViewController
- (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 = [NSURLURLWithString:urlString];
   
   
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
   
   
// 下载,代理.
   
   
// 创建网络连接,设置代理对象
   
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
   
   
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
    [conn
start];
   
}


#pragma NSURLConnectionDataDelegate
//接收到 响应头信息的时候就会调用.(最先调用的方法.),只会调用一次.
- (
void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
   
NSLog(@"%@ %@",response, [NSThreadcurrentThread]);
   

   
// 准备下载文件数据之前,实例化文件下载路径.
   
self.filePath= [NSStringstringWithFormat:@"/Users/teacher/Desktop/%@",response.suggestedFilename];
   
   
// 创建管道.
   
self.stream= [[NSOutputStreamalloc]initToFileAtPath:self.filePathappend:YES];
   
   
// 管道是需要手动开启的.
    [
self.streamopen];
}


//接收到 数据(实体内容)的时候就会调用.也会调用多次.
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
   
NSLog(@"本次接收到%ld 的数据 %@",data.length,[NSThreadcurrentThread]);
   
   
// 顺着管道,流入数据.
    [
self.streamwrite:[databytes]maxLength:data.length];
   
   
// NSLog(@"%@",data);

}

//网络完成之后(数据下载完毕),就会调用.
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection
{
   
NSLog(@"下载完毕...%@",[NSThreadcurrentThread]);
   
   
// 关闭管道.
    [
self.streamclose];
}



显示问题 )监听下载进度条的解决方法:
1.需要我那件总大小(定义一个属性 =respond。expectedContentLenght),当前下载 的数据量(定义一个属性 = data。length)——>BUG:直接设置进度条,进度条不能平滑是显示下载进度
#import"ViewController.h"

@interfaceViewController ()<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

@implementationViewController

-(
UIProgressView*)progressView
{
   
if (!_progressView) {
       
_progressView = [[UIProgressViewalloc]initWithFrame:CGRectMake(20,50,335,2)];
       
// 进度条颜色
       
_progressView.tintColor= [UIColorgreenColor];
       
        [
self.viewaddSubview:_progressView];
    }
   
return _progressView;
}


- (
void)viewDidLoad {
    [
superviewDidLoad];

   
// 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 = [NSURLURLWithString:urlString];
   
   
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
   
   
// 下载,代理.
   
   
// 创建网络连接,设置代理对象
   
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
   
   
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
    [conn
start];
   
}


#pragma NSURLConnectionDataDelegate
//接收到 响应头信息的时候就会调用.(最先调用的方法.),只会调用一次.
- (
void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
   
NSLog(@"%@ %@",response, [NSThreadcurrentThread]);
   
   
// 记录文件总大小
   
self.expectedLength= response.expectedContentLength;
   

   
// 准备下载文件数据之前,实例化文件下载路径.
   
self.filePath= [NSStringstringWithFormat:@"/Users/teacher/Desktop/%@",response.suggestedFilename];
   
   
// 创建管道.
   
self.stream= [[NSOutputStreamalloc]initToFileAtPath:self.filePathappend:YES];
   
   
// 管道是需要手动开启的.
    [
self.streamopen];
}


//接收到 数据(实体内容)的时候就会调用.也会调用多次.
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
  
// NSLog(@"本次接收到%ld 的数据 %@",data.length,[NSThread currentThread]);
   
   
// 记录已经下载的文件大小.
   
self.totalBytes+= data.length;
   
   
NSLog(@"%lld %lld %@",self.totalBytes,self.expectedLength,[NSThreadcurrentThread]);
   
   
self.progressView.progress= (float)self.totalBytes/self.expectedLength;
   
   
// 顺着管道,流入数据.
    [
self.streamwrite:[databytes]maxLength:data.length];
   
   
// NSLog(@"%@",data);

}

//网络完成之后(数据下载完毕),就会调用.
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection
{
   
NSLog(@"下载完毕...%@",[NSThreadcurrentThread]);
   
   
// 关闭管道.
    [
self.streamclose];
}

@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 = [NSURLURLWithString:urlString];
       
       
NSURLRequest *request = [NSURLRequestrequestWithURL:url];
       
       
// 下载,代理.
       
       
// 创建网络连接,设置代理对象
       
NSURLConnection *conn = [[NSURLConnectionalloc]initWithRequest:requestdelegate:self];
       
       
       
//将代理回调设置子线程。相当于NSDefaltModes模式将定时器添加在线程中.优先保证主线程的运行.
        [conn
setDelegateQueue:[[NSOperationQueuealloc]init]];
       
       
       
// 代理对象创建完毕之后,就会自动调用代理方法.不需要手动启动的.
        [conn
start];
       
// NSUrlConnectionDelegate 是一个特殊的事件源.代理方法想要执行,必须运行循环来执行.
       
       
// 手动开启运行循环:先添加事件源,再开启运行循环.
       
CFRunLoopRun();
       
       
// [[NSRunLoop currentRunLoop] run];
       
    });
  
}

#pragma mark - NSURLConnectionDataDelegate
////接收到 响应头信息的时候就会调用.(最先调用的方法.),只会调用一次.
- (
void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response{
   
      
// 记录文件总大小
   
self.expectedLength= response.expectedContentLength;
   
   
NSLog(@"响应头信息:%@---%@",response,[NSThreadcurrentThread]);
   
// // 准备下载文件数据之前,实例化文件下载路径.
   
self.filePath= [NSStringstringWithFormat:@"/Users/zzx/Desktop/%@",response.suggestedFilename];
   
   
//实例化管道
   
self.stream= [NSOutputStreamoutputStreamToFileAtPath:self.filePathappend:YES];
   
//开启管道
   
// 管道是需要手动开启的.
    [
self.streamopen];
   
}
//接收到 数据(实体内容)的时候就会调用.也会调用多次.
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data{
   
//    NSLog(@"%@ ---%@",[NSThread currentThread],data);
   
//每次调用,都把数据保存到定义的数据属性中
   
NSLog(@"本次接收到%ld 的数据 %@",data.length,[NSThreadcurrentThread]);
   
    
// 记录已经下载的文件大小.
   
self.totalBytes+= data.length;
   
   
dispatch_async(dispatch_get_main_queue(), ^{
       
       
//进度条进度
       
self.progress.progress= (float)self.totalBytes/self.expectedLength;
    });
   
   
  
// 顺着管道,流入数据.
    [
self.streamwrite:[databytes]maxLength:data.length];
   
}
//网络完成之后(数据下载完毕),就会调用.
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection
{
   
   
NSLog(@"下载完毕...%@",[NSThreadcurrentThread]);
//关闭管道
    [
self.streamclose];
}


3.BUG——>下载过程中,下载到本地的文件不能变大。
下载业务逻辑
    {
       
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"

@interfaceViewController ()<NSURLConnectionDataDelegate>

//定义一个数据保存到本地的地址属性
@property(nonatomic,copy)NSString* filePath;

//文件输入输出流
@property(nonatomic,strong)NSOutputStream* steam;

//下载进度条
@property(nonatomic,strong)UIProgressView* progressView;

//已经下载的数据量
@property(nonatomic,assign)longlong totalBtyes;

//文件总数据量
@property(nonatomic,assign)longlong expectedBtyesLength;

//本地文件大小
@property(nonatomic,assign)long long localFileLength;

@end

@implementationViewController



-(
UIProgressView*)progressView{
   
   
if (!_progressView) {
       
       
_progressView = [[ UIProgressView alloc]initWithFrame:CGRectMake(20,50,335,2)];
       
       
_progressView.tintColor= [ UIColor redColor];
       
        [
self.viewaddSubview:_progressView];
    }
   
return _progressView;
}

- (
void)viewDidLoad {
    [
superviewDidLoad];
   
   
NSString *urlString = @"http://127.0.0.1/dawenjian.zip";
   
   
self.filePath= @"/Users/zzx/Desktop/dawenjian";
   
//      1.先检查服务器文件大小.
    [
selfcheckServerFileWithUrlString:urlString];
   
       
// 2. 检查本地文件大小
    [
selfgetFilepathWithUrlString: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. 先检查服务器文件大小.
    [
selfcheckServerFileWithUrlString:urlString];
   
   
// 2. 检查本地文件大小
    [
selfgetFilepathWithUrlString:self.filePath];
   
   
if (self.localFileLength>= self.expectedBtyesLength) {//删除本地文件,并且重新开始下载
      
NSLog(@"删除本地文件,开始新文件的下载");
       
       
// 1. 删除本地文件
        [[
NSFileManagerdefaultManager]removeItemAtPath:self.filePatherror:NULL];
       
       
// 2. 重新开始下载
        [
selfgetFilepathWithUrlString: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 = [NSURLURLWithString:urlString];
           
           
NSMutableURLRequest *request = [NSMutableURLRequestrequestWithURL:url];
           
//X 位置 开始 ,下载到 文件末尾
           
NSString * range = [NSStringstringWithFormat:@"bytes=%lld-",self.localFileLength];
           
//告诉服务器,从哪里开始下载
            [request
setValue:rangeforHTTPHeaderField:@"Range"];
           
           
//创建网络连接。设置代理对象
           
NSURLConnection * conn = [[ NSURLConnection alloc]initWithRequest:requestdelegate:self];
           
           
// 将代理回调设置子线程.相当于NSDefaltModes 模式将定时器添加在线程中.优先保证主线程的运行.
            [conn
setDelegateQueue:[[NSOperationQueuealloc]init]];
           
            [conn
start];
          
// 手动开启运行循环:先添加事件源,再开启运行循环.
           
CFRunLoopRun();
        });

    }
}

#pragma mark -检查服务器文件大小( 同步 还是 异步?)  --同步请求.
- (void)checkServerFileWithUrlString:(NSString*)urlString{
   
    
// 只获取 response ,不要data.
   
   
   
NSURL *url = [NSURLURLWithString:urlString];
   
   
NSMutableURLRequest *request = [NSMutableURLRequestrequestWithURL:url];
   
// 设置请求方法:
   
// HEAD http 请求规定的方法. file协议没有这个方法.
   
// 这个方法只获得响应头信息,不会获取具体的文件内容.速度比较快,一般是使用同步请求发送的.
      request.
HTTPMethod= @"HEAD";
   
   
NSURLResponse * response = nil;
   
    [
NSURLConnectionsendSynchronousRequest:requestreturningResponse:&responseerror:NULL];
   
   
NSLog(@"%lld",response.expectedContentLength);
   
   
self.expectedBtyesLength= response.expectedContentLength;
 
}
#pragma mark -发送本地请求,获知本地文件大小

-(
void)getFilepathWithUrlString:(NSString*) urlString{
   
// 利用 NSFileManager 来检查本地文件大小.
   
   
// 获取文件管理器对象
   
// 检查这个路径下,是否有这个文件.
 
BOOL is_YES = [[NSFileManagerdefaultManager]fileExistsAtPath:self.filePath];
   
   
if (is_YES) {
       
       
NSLog(@"本地文件存在");
       
       
// 获取本地文件信息,文件信息中不包含文件类型.
       
       
NSDictionary * dict = [[ NSFileManager defaultManager]attributesOfItemAtPath:self.filePatherror: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, [NSThreadcurrentThread]);
   
//获取总数据量
//    self.expectedBtyesLength = response.expectedContentLength;
   
//   实例化接收数据的地址 地址名称随机最优
//    self.filePath = [NSString stringWithFormat:@"/Users/zzx/Desktop/%@",response.suggestedFilename];
   
//实例化管道
   
self.steam= [ NSOutputStream outputStreamToFileAtPath:self.filePathappend:YES];
   
//打开管道
    [
self.steamopen];
   
}

//发送网络请求,下载数据.
- (
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:requestdelegate:self];
       
       
//对代理回调在子线程中执行
        [conn
setDelegateQueue:[[NSOperationQueueallocinit]];
       
       
// 开启代理方法 ,也会自动开启
        [conn
start];
       
       
// 手动开启运行循环:先添加事件源,再开启运行循环.
       
CFRunLoopRun();
    });
   
   
   
}
//接收数据时会调用,持续调用,直到完成
- (
void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data{
    
// 记录已经下载的文件大小.
   
self.totalBtyes+= data.length;
   
   
   
NSLog(@"%lld %lld %@",self.localFileLength,self.expectedBtyesLength,[NSThreadcurrentThread]);

   
dispatch_async(dispatch_get_main_queue(), ^{
       
       
self.progressView.progress= (float)self.totalBtyes/self.expectedBtyesLength;
    });
   
   
// 顺着管道,流入数据.
    [
self.steamwrite:[databytes]maxLength:data.length];
}
//下载结束后调用,
- (
void)connectionDidFinishLoading:(NSURLConnection*)connection{
   
   
//关闭管道
    [
self.steamclose];
}

@end



大文件:  会造成内存暴涨{先将文件下载到内存中(data),然后再写入磁盘}.为了防止内存暴涨,不能直接使用block回调.
0 0
原创粉丝点击