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

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

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

NSOperation 版本

 typedef NS_ENUM(NSInteger, DownloadState){

     DownloadState_Ready = ,

     DownloadState_Suspend,

     DownloadState_Doing,

     DownloadState_Success,

     DownloadState_Cancel,

     DownloadState_Fail,
};

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

@protocol LNDownloaderDelegate ;

@interface LNDownloader : NSOperation

@property (nonatomic, weak)id<LNDownloaderDelegate>delegate;
@property (nonatomic, assign,readonly) DownloadState state; @property (nonatomic, assign,readonly) float downloadRate; @property (nonatomic, assign,readonly) float progress; @property (nonatomic, copy, readonly)NSURL *downloadURL; @property (nonatomic, copy, readonly)NSString *downloadPath; @property (nonatomic, copy, readonly)NSString *filePath;
@property (nonatomic, copy, readwrite)NSString *fileName; @property (nonatomic, strong, readonly) NSMutableURLRequest *fileRequest; @property (readonly,getter=isSuspend) BOOL isSuspend;

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

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

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

代理和block

/*

 利用代理回调的初始化方式;

 */

- (instancetype) initWithDownloadURL:(NSURL *)url
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;

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

- (void)cancelDownloaderAndRemoveFile:(BOOL)remove;

- (void)pause;

- (void)resume;

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

.m文件中:

//  Copyright © 2015年 Lee. All rights reserved.
// #define FILE_MANAGER [NSFileManager defaultManager]
#import "LNDownloader.h" @interface LNDownloader()<NSURLSessionDownloadDelegate> @property (nonatomic, strong) NSMutableURLRequest *fileRequest;
@property (nonatomic, copy) NSURL *downloadURL;
@property (nonatomic, copy) NSString *downloadPath;
@property (nonatomic, assign) DownloadState state;
@property (nonatomic, assign) float progress;
@property (nonatomic, strong) NSMutableData *receiveData;
@property (nonatomic, strong) NSData *resumeData; @property (nonatomic, assign)int64_t writtenByte;
@property (nonatomic, assign)int64_t expectTotalByte;
@property (nonatomic, strong)NSURLSession *session;
@property (nonatomic, strong)NSURLSessionDownloadTask *downloadTask; @property (nonatomic,copy)void(^progressBlock)(int64_t writtenByte,int64_t totalByte,float progress);
@property (nonatomic,copy)void(^errorBlock)(NSError *error);
@property (nonatomic,copy)void(^completeBlock)(BOOL downloadFinished, NSURLSessionDownloadTask *task);

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

 @dynamic isSuspend;
#pragma mark init
- (instancetype)initWithDownloadURL:(NSURL *)url
downloafPath:(NSString *)path
{
self = [super init];
if (self)
{
self.downloadURL = url;
self.state = DownloadState_Ready;
self.downloadPath = path;
self.fileRequest = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:];
}
return self;
} - (instancetype)initWithDownloadURL:(NSURL *)url
downloafPath:(NSString *)path
progress:(void (^)(int64_t, int64_t,float))progress
error:(void (^)(NSError *))error
complete:(void (^)(BOOL, NSURLSessionDownloadTask *))completBlock
{
self = [self initWithDownloadURL:url downloafPath:path];
if (self)
{
self.progressBlock = progress;
self.errorBlock = error;
self.completeBlock = completBlock;
}
return self;
}

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

- (void)start
{
[self willChangeValueForKey:@"isExecuting"];
self.state = DownloadState_Doing;
[self didChangeValueForKey:@"isExecuting"]; self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:nil]; self.downloadTask = [self.session downloadTaskWithRequest:self.fileRequest];
[self.downloadTask resume];
}

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

- (void)URLSession:(NSURLSession *)session

task:(NSURLSessionTask *)task

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

    if (error)
{
if (self.state == DownloadState_Suspend)
{ }
else
{
[self downloadWithError:error task:self.downloadTask];
} }

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

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

- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
self.writtenByte = totalBytesWritten;
self.expectTotalByte = totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{
if (self.progressBlock) {
self.progressBlock(totalBytesWritten,totalBytesExpectedToWrite,self.progress);
}
if (self.writtenByte *1.0 / self.expectTotalByte > 0.01 || self.writtenByte *1.0 /self.expectTotalByte >= 0.99)
{
self.progress = (float)self.writtenByte / self.expectTotalByte;
if ([self.delegate respondsToSelector:@selector(download:progress:)])
{
[self.delegate download:self progress:self.progress];
}
} });
} - (void)downloadWithError:(NSError *)error task:(NSURLSessionDownloadTask *)task
{
if (error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.errorBlock)
{
self.errorBlock(error);
}
if ([self.delegate respondsToSelector:@selector(download:didStopWithError:)]) {
[self.delegate download:self didStopWithError:error];
}
});
}
BOOL success = error == nil; dispatch_async(dispatch_get_main_queue(), ^{ if (self.completeBlock)
{
self.completeBlock(success,task);
}
if (self.state == DownloadState_Suspend) {
return ;
}
if ([self.delegate respondsToSelector:@selector(download:didFinishWithSuccess:atPath:)]) {
[self.delegate download:self didFinishWithSuccess:success atPath:[self.downloadPath stringByAppendingPathComponent:self.fileName]];
} });
if (self.state == DownloadState_Suspend) {
return ;
}
DownloadState state = success ? DownloadState_Success :DownloadState_Fail;
[self cancelAndSetDownloadStateWhenDownloadFinish:state];
} - (void)cancelAndSetDownloadStateWhenDownloadFinish:(DownloadState)state
{
[self.downloadTask cancel];
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
self.state = state;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
- (void)cancelDownloaderAndRemoveFile:(BOOL)remove
{
[self.downloadTask cancel];
self.delegate = nil;
[self removeFile];
[self cancel];
}

  

写一个基于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. Android中SharedPreferences使用方法介绍

    一.Android SharedPreferences的简介 SharedPreferences是一种轻型的Android数据存储方式,它的本质是基于XML文件存储key-value键值对数据,通常用 ...

  2. updatepanel刷新后重新加载js脚本问题

    在页尾加 <script type="text/javascript"> Sys.WebForms.PageRequestManager.getInstance().a ...

  3. 数据结构(树链剖分,线段树):SDOI 2016 游戏

    4515: [Sdoi2016]游戏 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 351  Solved: 157[Submit][Status][ ...

  4. Unity Dx9 Occlusion Query plugin

    //Occlusion Unity plugin #include "UnityPluginInterface.h" #include <math.h>#include ...

  5. Elasticsearch教程之基础概念

    基础概念 Elasticsearch有几个核心概念.从一开始理解这些概念会对整个学习过程有莫大的帮助. 1.接近实时(NRT)        Elasticsearch是一个接近实时的搜索平台.这意味 ...

  6. 数据结构——foodfill 八连块问题

    Description Due to recent rains, water has pooled in various places in Farmer John's field, which is ...

  7. Sublime Text 3安装与使用 Package Control 插件安装

    原文地址:http://www.cnblogs.com/zhcncn/p/4113589.html 本文是Sublime Text 全程指引 by Lucida (http://www.cnblogs ...

  8. Bellman-Bord(贝尔曼-福特)

    include const int inf=0x3f3f3f3f; int main() { int m,n; scanf("%d%d",&n,&m); int u ...

  9. python数据的存储和持久化操作

    Python的数据持久化操作主要是六类:普通文件.DBM文件.Pickled对象存储.shelve对象存储.对象数据库存储.关系数据库存储. 普通文件不解释了,DBM就是把字符串的键值对存储在文件里: ...

  10. js~一个列表中包含上移下移删除等功能

    最近做了一个项目,包括了一个列表页,为了用户体验,操作均使用JS实现,其中包括在列表中实现上移,下移,删除等功能,前台JS,后端数据修改使用AJAX,本文主要说一下前台JS这块 先看一下页面的截图