前段时间AFNetworking 更新到3.0 ,彻底废弃NSURLConnection的API转由NSURLSession来实现,代码并没有改动很大,AF封装的很好了,读完源码感觉收获很大。

下载不可避免的会遇到多线程异步下载问题,iOS中多线程解决方案一般是GCD和NSOperation。为了下载器便于管理,这里是创建一个类继承于NSOperation重写其start方法即可(GCD版本基本一致,最后会提到其实现代码和思路)

相关代码和下载库地址都在这里:https://github.com/LeeBlaze/LNDownload.git

NSOperation 版本

  1. typedef NS_ENUM(NSInteger, DownloadState){
  2.  
  3. DownloadState_Ready = ,
  4.  
  5. DownloadState_Suspend,
  6.  
  7. DownloadState_Doing,
  8.  
  9. DownloadState_Success,
  10.  
  11. DownloadState_Cancel,
  12.  
  13. DownloadState_Fail,
  14. };

首先定义了下载状态的枚举类型,不多解释,下面看看相关的属性

  1. @protocol LNDownloaderDelegate ;
  2.  
  3. @interface LNDownloader : NSOperation
  4.  
  5. @property (nonatomic, weak)id<LNDownloaderDelegate>delegate;
  6. @property (nonatomic, assign,readonly) DownloadState state;
  7.  
  8. @property (nonatomic, assign,readonly) float downloadRate;
  9.  
  10. @property (nonatomic, assign,readonly) float progress;
  11.  
  12. @property (nonatomic, copy, readonly)NSURL *downloadURL;
  13.  
  14. @property (nonatomic, copy, readonly)NSString *downloadPath;
  15.  
  16. @property (nonatomic, copy, readonly)NSString *filePath;
  17. @property (nonatomic, copy, readwrite)NSString *fileName;
  18.  
  19. @property (nonatomic, strong, readonly) NSMutableURLRequest *fileRequest;
  20.  
  21. @property (readonly,getter=isSuspend) BOOL isSuspend;

一开始声明了一个代理,用来提供下载进度,下载状态和下载地址等信息,相关代码待会再说。

可以看到几乎所有属性都是只读状态,这里避免用户对下载状态,进度,下载地址等进行更改,由下载库统一管理,只暴露出相关只读属性返回当前任务状态进度等相关信息(只读属性的赋值参见KVO

在下载回调时,一般有两种常用方式,代理和block,AF中采用了block得方式,这里提供两种方式:

代理和block

  1. /*
  2.  
  3. 利用代理回调的初始化方式;
  4.  
  5. */
  6.  
  7. - (instancetype) initWithDownloadURL:(NSURL *)url
  8. downloafPath:(NSString *)path;

/*

block回调的初始化方式;

*/

- (instancetype) initWithDownloadURL:(NSURL *)url

downloafPath:(NSString *)path

progress:(void (^)(int64_t writtenByte,int64_t totalByte,float progress))progress

error:(void (^)(NSError *error))error

complete:(void (^)(BOOL downloadFinished, NSURLSessionDownloadTask *task))completBlock;

当然还要有下载的开启,取消,删除等方法:

  1. - (void)cancelDownloaderAndRemoveFile:(BOOL)remove;
  2.  
  3. - (void)pause;
  4.  
  5. - (void)resume;

下载库写的比较简略,一些对于错误的处理没有涉及,实际项目中可以加上。

.m文件中:

  1. // Copyright © 2015年 Lee. All rights reserved.
  2. //
  3.  
  4. #define FILE_MANAGER [NSFileManager defaultManager]
  5. #import "LNDownloader.h"
  6.  
  7. @interface LNDownloader()<NSURLSessionDownloadDelegate>
  8.  
  9. @property (nonatomic, strong) NSMutableURLRequest *fileRequest;
  10. @property (nonatomic, copy) NSURL *downloadURL;
  11. @property (nonatomic, copy) NSString *downloadPath;
  12. @property (nonatomic, assign) DownloadState state;
  13. @property (nonatomic, assign) float progress;
  14. @property (nonatomic, strong) NSMutableData *receiveData;
  15. @property (nonatomic, strong) NSData *resumeData;
  16.  
  17. @property (nonatomic, assign)int64_t writtenByte;
  18. @property (nonatomic, assign)int64_t expectTotalByte;
  19. @property (nonatomic, strong)NSURLSession *session;
  20. @property (nonatomic, strong)NSURLSessionDownloadTask *downloadTask;
  21.  
  22. @property (nonatomic,copy)void(^progressBlock)(int64_t writtenByte,int64_t totalByte,float progress);
  23. @property (nonatomic,copy)void(^errorBlock)(NSError *error);
  24. @property (nonatomic,copy)void(^completeBlock)(BOOL downloadFinished, NSURLSessionDownloadTask *task);

这里定义了三个block,第一个用来回调下载进度,第二个是下载失败的回调,第三个是下载完成的回调。因为我们在.h文件中声明了一个isSuspend属性,算是对NSOperation属性的一个扩充吧,在.m中我们要实现其getter方法,因此我们需要@dynamic  isSuspend

  1. @dynamic isSuspend;
  2. #pragma mark init
  3. - (instancetype)initWithDownloadURL:(NSURL *)url
  4. downloafPath:(NSString *)path
  5. {
  6. self = [super init];
  7. if (self)
  8. {
  9. self.downloadURL = url;
  10. self.state = DownloadState_Ready;
  11. self.downloadPath = path;
  12. self.fileRequest = [[NSMutableURLRequest alloc] initWithURL:url
  13. cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:];
  14. }
  15. return self;
  16. }
  17.  
  18. - (instancetype)initWithDownloadURL:(NSURL *)url
  19. downloafPath:(NSString *)path
  20. progress:(void (^)(int64_t, int64_t,float))progress
  21. error:(void (^)(NSError *))error
  22. complete:(void (^)(BOOL, NSURLSessionDownloadTask *))completBlock
  23. {
  24. self = [self initWithDownloadURL:url downloafPath:path];
  25. if (self)
  26. {
  27. self.progressBlock = progress;
  28. self.errorBlock = error;
  29. self.completeBlock = completBlock;
  30. }
  31. return self;
  32. }

在初始化方法中,分别对downloadUrl state downloadPath和fileRequest等进行了赋值和request的初始化,对于一些progress state属性等是readonly声明,所以在内部赋值的时候需要使用KVC

  1. - (void)start
  2. {
  3. [self willChangeValueForKey:@"isExecuting"];
  4. self.state = DownloadState_Doing;
  5. [self didChangeValueForKey:@"isExecuting"];
  6.  
  7. self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
  8. delegate:self
  9. delegateQueue:nil];
  10.  
  11. self.downloadTask = [self.session downloadTaskWithRequest:self.fileRequest];
  12. [self.downloadTask resume];
  13. }

在重写resmue的时候因为可能是暂停下载任务继续下载的情况,所以通过判断resumeData是否为空来获得或新建对应的task.下载的进度和状态完成情况都是通过代理回调给我们需要注意的是

- (void)URLSession:(NSURLSession *)session

task:(NSURLSessionTask *)task

didCompleteWithError:(NSError *)error这个方法在我们暂停或者下载失败的时候都会回调,因此我们需要通过当前task的状态进行判断:

  1. if (error)
  2. {
  3. if (self.state == DownloadState_Suspend)
  4. {
  5.  
  6. }
  7. else
  8. {
  9. [self downloadWithError:error task:self.downloadTask];
  10. }
  11.  
  12. }

  在暂停时,这里没有做任何处理。

下载的progress和状态等通过通知和代理传值,监听相关的通知即可获取需要的所有信息:

  1. - (void)URLSession:(NSURLSession *)session
  2. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  3. didWriteData:(int64_t)bytesWritten
  4. totalBytesWritten:(int64_t)totalBytesWritten
  5. totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
  6. {
  7. self.writtenByte = totalBytesWritten;
  8. self.expectTotalByte = totalBytesExpectedToWrite;
  9.  
  10. dispatch_async(dispatch_get_main_queue(), ^{
  11. if (self.progressBlock) {
  12. self.progressBlock(totalBytesWritten,totalBytesExpectedToWrite,self.progress);
  13. }
  14. if (self.writtenByte *1.0 / self.expectTotalByte > 0.01 || self.writtenByte *1.0 /self.expectTotalByte >= 0.99)
  15. {
  16. self.progress = (float)self.writtenByte / self.expectTotalByte;
  17. if ([self.delegate respondsToSelector:@selector(download:progress:)])
  18. {
  19. [self.delegate download:self progress:self.progress];
  20. }
  21. }
  22.  
  23. });
  24. }
  25.  
  26. - (void)downloadWithError:(NSError *)error task:(NSURLSessionDownloadTask *)task
  27. {
  28. if (error)
  29. {
  30. dispatch_async(dispatch_get_main_queue(), ^{
  31. if (self.errorBlock)
  32. {
  33. self.errorBlock(error);
  34. }
  35. if ([self.delegate respondsToSelector:@selector(download:didStopWithError:)]) {
  36. [self.delegate download:self didStopWithError:error];
  37. }
  38. });
  39. }
  40. BOOL success = error == nil;
  41.  
  42. dispatch_async(dispatch_get_main_queue(), ^{
  43.  
  44. if (self.completeBlock)
  45. {
  46. self.completeBlock(success,task);
  47. }
  48. if (self.state == DownloadState_Suspend) {
  49. return ;
  50. }
  51. if ([self.delegate respondsToSelector:@selector(download:didFinishWithSuccess:atPath:)]) {
  52. [self.delegate download:self didFinishWithSuccess:success atPath:[self.downloadPath stringByAppendingPathComponent:self.fileName]];
  53. }
  54.  
  55. });
  56. if (self.state == DownloadState_Suspend) {
  57. return ;
  58. }
  59. DownloadState state = success ? DownloadState_Success :DownloadState_Fail;
  60. [self cancelAndSetDownloadStateWhenDownloadFinish:state];
  61. }
  62.  
  63. - (void)cancelAndSetDownloadStateWhenDownloadFinish:(DownloadState)state
  64. {
  65. [self.downloadTask cancel];
  66. [self willChangeValueForKey:@"isFinished"];
  67. [self willChangeValueForKey:@"isExecuting"];
  68. self.state = state;
  69. [self didChangeValueForKey:@"isExecuting"];
  70. [self didChangeValueForKey:@"isFinished"];
  71. }
  72. - (void)cancelDownloaderAndRemoveFile:(BOOL)remove
  73. {
  74. [self.downloadTask cancel];
  75. self.delegate = nil;
  76. [self removeFile];
  77. [self cancel];
  78. }

  

写一个基于NSURLSession的网络下载库的更多相关文章

  1. 教你如何使用Java手写一个基于链表的队列

    在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...

  2. 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发

    ''' 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发 ''' client import socket import time client = socket.socket() clie ...

  3. 写一个nginx.conf方便用于下载某个网页的所有资源

    写一个nginx.conf方便用于下载某个网页的所有资源 worker_processes 1; events { worker_connections 1024; } http { include ...

  4. 网络编程—【自己动手】用C语言写一个基于服务器和客户端(TCP)!

    如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字). socket(套接字):简单来讲,socket就是用于描述IP地 ...

  5. 用Python+Aria2写一个自动选择最优下载方式的E站爬虫

    前言 E站爬虫在网上已经有很多了,但多数都只能以图片为单位下载,且偶尔会遇到图片加载失败的情况:熟悉E站的朋友们应该知道,E站许多资源都是有提供BT种子的,而且通常打包的是比默认看图模式更高清的文件: ...

  6. [PHP]用PHP自己写一个基于zoomeye的api(偷懒必备quq)

    0x01 起因 因为手速慢,漏洞刷不过别人,一个个手补确实慢,所以想自己写一个api,一键抓取zoomeye的20页,然后就可以打批量了 ovo(真是太妙了!) 0x02 动工       1.抓包做 ...

  7. 如何用nfs命令烧写内核和文件系统(网络下载文件到nandflash)(未完)

    使用tftp下载烧写 a.设uboot里的ip地址 set ipaddr 192.168.1.17(uboot的ip设置成同网段) set serverip 192.168.1.5(电脑本机作为服务i ...

  8. 手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)

    前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素 ...

  9. 教你如何使用Java手写一个基于数组实现的队列

    一.概述 队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表.在具体应用中通常用链表或者数组来实现.队列只允许在后端(称为rear)进行插入操作,在 ...

随机推荐

  1. 正则表达式:根据逗号解析CSV并忽略引号内的逗号

    需求:解析CSV文件并忽略引号内的逗号 解决方案: public static void main(String[] args) { String s = "a,b,c,\"1,0 ...

  2. JavaScript Maintainable

    1. Avoid conflict with Native Variable namespace

  3. LINUX下安装ORACLE,完全搞定

    参考文档: http://www.tuicool.com/articles/eE3mmy http://blog.chinaunix.net/uid-11209572-id-3599052.html

  4. rsync使用说明

    需求:把10.5.128.190数据同步到10.5.128.27 用客服端-服务器模式,需要从客户端发起 也就是从10.5.128.27发起 10.5.128.27 作为客户端 10.5.128.19 ...

  5. Tribles(概率)

    Description   Problem ATribblesInput: Standard Input Output: Standard Output GRAVITATION, n."Th ...

  6. 数据结构(块状链表):COGS 1689. [HNOI2010]Bounce 弹飞绵羊

    时间限制:1 s   内存限制:259 MB [题目描述] 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地 ...

  7. 动态规划 HDU 1176

    免费馅饼 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  8. lightoj 1026 无向图 求桥

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1026 #include<cstdio> #include<cstri ...

  9. selenium grid 测试资料

    像风一样自由的4篇博客: http://blog.csdn.net/five3/article/details/9671287 http://blog.csdn.net/five3/article/d ...

  10. Windows的应用管理工具 PortableApps,Chocolatey和Ninite

    以前为了让我的firefox变成portable的,我使用了PortableApps,它不只是做软件的绿色版,而且也是一个软件的管理平台,可以通过它来管理软件的更新,挺方便的. 前段时间试用Scrip ...