写一个基于NSURLSession的网络下载库
前段时间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的网络下载库的更多相关文章
- 教你如何使用Java手写一个基于链表的队列
在上一篇博客[教你如何使用Java手写一个基于数组的队列]中已经介绍了队列,以及Java语言中对队列的实现,对队列不是很了解的可以我上一篇文章.那么,现在就直接进入主题吧. 这篇博客主要讲解的是如何使 ...
- 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发
''' 写一个基于TCP协议套接字,服务端实现接收客户端的连接并发 ''' client import socket import time client = socket.socket() clie ...
- 写一个nginx.conf方便用于下载某个网页的所有资源
写一个nginx.conf方便用于下载某个网页的所有资源 worker_processes 1; events { worker_connections 1024; } http { include ...
- 网络编程—【自己动手】用C语言写一个基于服务器和客户端(TCP)!
如果想要自己写一个服务器和客户端,我们需要掌握一定的网络编程技术,个人认为,网络编程中最关键的就是这个东西--socket(套接字). socket(套接字):简单来讲,socket就是用于描述IP地 ...
- 用Python+Aria2写一个自动选择最优下载方式的E站爬虫
前言 E站爬虫在网上已经有很多了,但多数都只能以图片为单位下载,且偶尔会遇到图片加载失败的情况:熟悉E站的朋友们应该知道,E站许多资源都是有提供BT种子的,而且通常打包的是比默认看图模式更高清的文件: ...
- [PHP]用PHP自己写一个基于zoomeye的api(偷懒必备quq)
0x01 起因 因为手速慢,漏洞刷不过别人,一个个手补确实慢,所以想自己写一个api,一键抓取zoomeye的20页,然后就可以打批量了 ovo(真是太妙了!) 0x02 动工 1.抓包做 ...
- 如何用nfs命令烧写内核和文件系统(网络下载文件到nandflash)(未完)
使用tftp下载烧写 a.设uboot里的ip地址 set ipaddr 192.168.1.17(uboot的ip设置成同网段) set serverip 192.168.1.5(电脑本机作为服务i ...
- 手把手写一个基于Spring Boot框架下的参数校验组件(JSR-303)
前言 之前参与的新开放平台研发的过程中,由于不同的接口需要对不同的入参进行校验,这就涉及到通用参数的校验封装,如果不进行封装,那么写出来的校验代码将会风格不统一.校验工具类不一致.维护风险高等其它因素 ...
- 教你如何使用Java手写一个基于数组实现的队列
一.概述 队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表.在具体应用中通常用链表或者数组来实现.队列只允许在后端(称为rear)进行插入操作,在 ...
随机推荐
- AVL树的算法思路整理
http://www.cnblogs.com/heqile/archive/2011/11/28/2265713.html 看完了<数据结构与算法分析(C++描述)>的4.4节AVL树,做 ...
- Unity 弹出界面时屏蔽对3D场景的点击
注:这里的UI制作用的是NGUI插件 如题,在游戏中经常会遇到这种情况,场景中点击相关物体或者按钮弹出对应的2D界面,这时候除了2D界面上的可点击按钮等,应该屏蔽掉对3D场景的点击或者拖动事件. 在这 ...
- centos6.5安装gcc6.1等c++环境
centos6.5安装gcc6.1等c++环境 1.获取gcc安装包并解压wget http://ftp.gnu.org/gnu/gcc/gcc-6.1.0/gcc-6.1.0.tar.bz2tar ...
- ♫【JS基础】壹零零壹
如何面试一个前端开发者? function spacify(str) { return str.split('').join(' ') } console.log(spacify('hello wor ...
- (转载)URL与URI的区别
(转载)http://blog.csdn.net/eagle51998/article/details/372052 1 URL(Uniform Resoure Locator:统一资源定位器)是W ...
- PowerDesigner自定义Word导出格式
本文主要讲解如何将PDM的表结构导出成word以及如何自定义导出格式和显示效果,希望对大家有所帮助...(以下步骤以PowerDesigner 15版本为例) 一.新建导出报表,进入报表设计界面: 1 ...
- 使用国内镜像通过pip安装python的一些包 Cannot fetch index base URL http://pypi.python.org/simple/
原文地址:http://www.xuebuyuan.com/1157602.html 学习flask,安装virtualenv环境,这些带都ok,但是一安装包总是出错无法安装, 比如这样超时的问题: ...
- [Audio processing] 数据集生成 & 性别年龄分类训练 Python
1.重命名,Python中文路径各种错误,所以需要先将所有文件的路径名全都改成中文.用的是MAC系统,所以WIN下的命令行批处理没法解决,所以用C来完成 // Created by Carl on 1 ...
- python面向对象【进阶篇】
静态方法 通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量 ...
- linux —— 学习笔记(软件操作:安装、卸载、执行)
目录: 0.相关基本命令 1.安装软件 2.卸载软件 3.打开软件 0.相关基本命令 与软件操作相关的主要命令有:dpkg 和 apt-get . dpkg : “dpkg ...