使用NSURLConnection实现断点下载

1.使用GET请求直接去服务器下载文件
1.1 内存暴涨
1.2 下载进度无法检测

2.解决内存暴涨的问题 : 使用NSURLConnection的代理去实现下载

3.获取文件下载进度 : 使用NSURLConnection的代理去实现下载
    文件的总大小 : response
    文件的当前下载的总大小 : 拼接每次下载的数据的大小

4.解决代理方法执行的线程的问题
    4.1 获取数据的代理方法频繁调用,不能在主线程执行
    4.2 我们查看设置代理的方法的头文件,发现,设置代理的线程和代理方法调用的线程是一样的
    4.3 为了让代理方法在子线程执行,我们需要子线程中设置代理
    4.4 要让子线程中的代理方法执行,需要子线程开启运行循环,子线程的运行循环默认不开启,我们需要手动开启.

5.实现压缩包的沙盒缓存 (千万不要实现内存缓存,因为压缩包非常大)
    NSMutableData
    NSFileHandle
    NSOutputStream  OK

6.实现断点下载,在实现断点下载之前,我们做了HEAD请求的演练
    6.1 HEAD请求的作用 : 只能获取到响应头,不能获取响应体;
    6.2 异步的HEAD请求
        我们可以在获取响应头的同时,还可以做其他事情 
    6.3 同步的HEAD请求
        只有同步任务执行完,我们才能做其他事情

        为什么同步的HEAD请求没有回调?
        因为任务是同步的,任务执行完,我们"自然而然"就可以得到数据(不需要异步监听任务什么时候执行完)

        为什么同步任务有返回值?而异步任务没有?
        因为异步任务不确定什么时候能够执行完,所以需要传入代码块,什么时候执行完,就什么时候回调数据

        为什么同步任务需要定义输出参数?
        因为一个方法只有一个返回值,如果你的同步方法需要返回多个值,其他的就需要定义成输出参数

    总结:
     a).同步的请求有返回值


     b).异步的请求没有回调


     c).参数


        参数1(输出参数) : 响应对象的地址


        参数2(输出参数) : 错误信息的地址


     d).为什么同步的请求有data返回值?


        因为同步的任务,不要做异步的监听,代码执行完就有值,所以data直接可以放在方法的返回值里面


     e).为什么response要设计成输出参数?


        因为同步任务不需要做异步监听,而且方法的返回有且只能有一个,没办法同时返回多个,如果一个方法中有多个需                                要返回的值,其他的就需要定义成输出参数

7.分析了断点下载的原理和步骤
    7.1 获取服务器文件总大小
    7.2 获取本地文件总大小
    7.3 本地文件大小和服务器文件大小进行比较
    7.4 提示 : 比较的时候,我们需要这个比较的结果.
    7.5 为什么需要比较的结果?
        因为比较的结果,我们可以拿来设置到requestM里面的Range字段,告诉服务器从哪个位置开始给客户端续传数据
    7.6 比较的过程
        本地文件 == 服务器文件 (文件已经正确的下载完,再次点击下载按钮时,直接return)
        本地文件 > 服务器文件 (文件下载错误,删除,从0开始下载)
        本地文件 < 服务器文件 (文件没有下载完,需要继续下载,就是断点下载 / 断点续传)

8.下载过程的回调
    文件下载完成的回调
    文件下载进度的回调
    文件下载出错的回调
    在使用代码块时,我们需要注意循环引用

9.文件下载器管理类
    9.1 设计成单例模式;因为文件下载器有可能在APP的很多地方都要用到
    9.2 管理文件下载
    9.3 管理文件暂停下载
    9.4 解决重复下载的问题
        设计下载器缓存池
            文件下载完需要移除下载器
            文件下载出错需要移除下载器
            文件下载被暂停后需要移除下载器

10.自定义NSOperation实现了文件下载器
    10.1 在重写main方法时,发送HEAD请求获取响应头信息
    10.2 在重写main方法时,比较了本地文件和服务器文件大小
    10.3 在重写main方法时,设置了NSURLConnection的代理
    10.4 现在FileDownloader继承自NSOperation的,那么在使用的时候,需要把它添加到队列

// 三.————————–NSURLConnectionDataDelegate—————————-

代理方法介绍

获取响应

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

获取本次接收的文件数据,一点儿一点儿接收文件数据

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

文件下载完成

- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

监听下载是否出错 : NSURLConnectionDelegate(父协议)的代理方法

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

data.length//获取二进制数据的长度
观察结果_问题1

文件下载是耗时操作且调用频繁,就要留意这个方法执行的线程了,如果是在主线程中执行的.就会卡死主界面.打印线程发现,这个代理方法是在主线程中执行的.

// 四.——————————NSFileHandle———————————-

NSFileHandle 使用步骤

1.获取文件保存的路径
2.创建NSFileHandle对象.
3.判断要下载的文件是否存在.
4.如果文件不存在就新建一个文件,并保存数据.
5.如果文件存在就继续在当前文件下保存.并确定从哪儿开始保存.
6.开始保存.
7.保存完了以后要关闭NSFileHandle.
使用步骤:每写入一次就需要创建一个handle对象

    // 文件保存的路径
    NSString *filePath = @"/Users/zhangjie/Desktop/sogou.zip";
    // 1. 创建NSFileHandle对象.
    NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    // 2. 判断要下载的文件是否存在.
    if (handle == nil) {
        // 3. 如果文件不存在就新建一个文件,并保存数据.
        // 这中方式也是可以方便快捷的创建一个文件的
        [data writeToFile:filePath atomically:YES];
    } else {
        // 4. 如果文件存在就继续在当前文件下保存.并确定从哪儿开始保存.
        // 每次都从文件的末尾开始保存,就不会出错.
        [handle seekToEndOfFile];
        // 5. 开始保存
        [handle writeData:data];
        // 6. 保存完了以后要关闭NSFileHandle.
        [handle closeFile];
    }

// 五.——————————NSOutputStream———————————-

NSOutputStream使用步骤 :

1.获取文件保存的路径.
2.客户端跟服务器建立好链接,得到响应体之后就创建好管道,并打开管道.
3.每次接收一点数据就传输一点数据.
4.文件下载完成,关闭管道.

 // 文件保存的路径
    NSString *filePath = @"/Users/zhangjie/Desktop/sogou.zip";
    // 创建管道
    self.stream = [NSOutputStream outputStreamToFileAtPath:filePath append:YES];
    // 打开管道
    [self.stream open];
    // 传输数据 : 内存-->沙盒,下载一点儿就传输一点儿
    [self.stream write:data.bytes maxLength:data.length];
    // 文件下载完成就关闭管道
    [self.stream close];

data.bytes//将二进制数据分成一个一个字节

// 六.———————————-断点续传————————————–

断点续传实现思路

先判断本地文件是否存在,如果文件不存在,从0开始下载.
如果本地文件存在
1.判断本地文件 == 服务器文件,不需要下载.
2.判断本地文件 < 服务器文件,从当前位置开始下载.
3.判断本地文件 > 服务器文件,删除之前的文件,从0开始下载.

断点续传实现步骤 :

1.获取服务器文件大小
2.获取本地文件大小
3.比较两个文件大小
4.得到比较的结果再发送GET请求利用代理下载文件

注意 : 这四步是要有顺序的.后面一步都是以前面那一步的结果为依据的.所以要同步执行.
提示 : 拿到比较的结果,即当前文件的大小(0,下了部分,已经下完).然后可以告诉服务器从哪开始去下载文件.

我们比较的目的就是为了得到本地当前文件的大小,有了本地当前文件的大小,就知道从哪儿继续下载了

获取服务器文件大小

获取服务器文件大小就是发送同步的HEAD请求.获取到响应头,并从中取出服务器文件大小.

如果发送异步请求,有可能文件已经在比较了,但是服务器的文件大小还没有获取到.
提示 : 拿到响应头之后,还可以顺便取出文件名,用于拼接文件保存到沙盒的路径.

HEAD请求简介

不获取响应体data,只获取响应头NSURLResponse.
一般在真正下载文件之前先同步获取文件的大小.

同步HEAD请求使用场景

当我们要下载一个文件,在下载之前需要知道这个文件的一些基本信息时.需要用到HEAD请求
比如先拿到文件总大小,计算下载进度和断点续传.
比如先拿到文件的文件名称,就可以在下载完成之前确定好文件保存的路径.
// 获取本地文件大小

    NSFileManager *manager = [NSFileManager defaultManager];
    NSDictionary *attrs = [manager attributesOfItemAtPath:self.filePath error:NULL];//文件信息

[manager fileExistsAtPath:self.filePath];//文件是否存在

// 移除文件
[manager removeItemAtPath:self.filePath error:NULL];

在网页文件下载中,请求头里面有个Range字段.
Range字段就是告诉服务器从文件的第几个字节开始下载.
Range: bytes=x-y 表示从x字节下载到y字节
Range: bytes=x- 表示从x字节下载到文件末尾.断点续传
Range: bytes=-y 表示从头下载到y字节
断点续传的核心

发送GET请求
设置请求头中的Range字段,告诉服务器从哪儿开始继续下载.
// 设置请求头,告诉服务器从哪儿开始下载文件
[requestM setValue:[NSString stringWithFormat:@”bytes=%lld-“,self.currentLength] forHTTPHeaderField:@”Range”];

文件下载_暂停下载
暂停下载,其实就是取消NSURLConnection的网络下载操作.调用他的cancel方法.
继续下载,其实就是断点续传.不需要再另外实现.

//暂停其实是将下载链接杀死,链接没了就不会执行下载的代理方法,如果要继续下载任务,就需要重新创建下载链接对象
[self.connection cancel];

目前存在的问题 : 当我们在文件下载的过程中,不断的点击”下载”按钮,即不断的创建新的下载任务时.会出现多下载任务下载这同一个文件的情况.

解决重复下载的思路

创建下载管理类(单例设计模式),管理所有的下载操作.
管理类里面定义一个缓存池,专门缓存已经创建的下载器.
在新建下载器之前判断缓存池里有没有,有就不再新建下载器,没有就新建下载器.
1. 获取服务器文件大小是发送的同步HEAD请求,需要在子线程中执行的.
2. 获取本地文件大小,并与服务器文件比较大小是I/O操作也是可以在子线程执行的.
3. 设置代理也是要在子线程执行的
结论 : 以上三步,要有序的在子线程中执行.
将FileDownloader的继承改成NSOperation,直接就将FileDownloader设计成子线程的环境.这样就不需要再另外的分别开启子线程了.
实现步骤

  1. 将FileDownloader的继承改成NSOperation.
  2. 重写NSOperation的main方法.
  3. 在main方法里面实现获取服务器文件大小–>获取本地文件大小,并与服务器文件比较大小–>设置代理.

当我们把FileDownloader这个类改成NSOperation时.文件的暂停下载不光可以取消connection的网络下载操作,还可以取消Operation.
[connection cancel] : 取消的是真正网络下载操作.
[Operation cancel] : 可以取消跟下载相关的所有代码,即可以减少下载时代码的执行量.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值