基于iOS 10、realm封装的下载器

本文详细介绍了在iOS 10环境下,使用NSURLSession实现带有断点续传、后台下载功能的下载器的封装过程。涉及到下载、后台下载、APP被杀死后的断点续传、网络中断后的恢复下载,以及如何存储下载信息。文章通过代码实例和分析,讲解了下载模块的各个关键部分,并提供了完整的项目文件链接。
摘要由CSDN通过智能技术生成

代码地址如下:
http://www.demodashi.com/demo/11653.html

概要

在决定自己封装一个下载器前,我本以为没有那么复杂,可在实际开发过程中困难重重,再加上iOS10和Xcode8的发布,更是带来一些意外的麻烦,断断续续过了一个多月的时间才弄出一个可用的版本。目前网上关于iOS10下载模块出现的bug以及一些特殊情况如何处理的文章比较少,最起码我还没有看到过,这里抛砖引玉,给小伙伴们提供一些思路,也算是这篇文章存在的一点点价值。

公司一个音频项目的下载模块使用的是第三方的,总是会出现无法正常下载等问题,并且由于很难短时间内了解这个颇为庞大复杂的第三方库,所有比较难以解决出现的bug,因此我决定自己封装一个。当然网上会找到一些基于ASI封装的下载器,下载demo简单试用后均没发现什么问题,但是我还是弃用了,主要原因是怕出现问题,由于不了解这些第三方库和ASI而无法解决,另一方面确实不想再将ASI引入到项目里了,同时我觉得也确实应该好好研究下这方面的知识了。

在开发过程中发现这个太过频繁使用的功能在iOS端并不那么容易做好,基于Apple自己的接口开发确实比较难实现我们常用的下载需求,这或许就是AFN一直没有很好的实现下载模块的原因,AFN对下载的封装,完全基于Apple自己的接口简单的封装,其实和直接Apple的接口区别并不大,所以想直接使用AFN实现较为复杂下载功能的小伙伴可能要失望了。

下面说明下本文的讲解思路,主要是按照下载功能进行模块化的讲解,比如下载、断点续传、删除信息、更新信息等,单个功能分开阐述,比较利于理解,也方便大家分不同的时间阅读,避免一口气读完如此长的技术性文章的厌烦感,同时分模块阐述后大家觉得有用的可以借鉴下,觉得没用的大可当糟粕一样弃之。

注意
1、本文不敢妄称封装了可以直接在项目中使用的库。一方面由于我自己只是写了一个demo测试,还没有在实际的项目中应用测试;另一方面由于这里针对了iOS10以后苹果出现的下载的bug进行了特殊处理,后续苹果的API更新有可能会有变化。
2、本文旨在给有需求的小伙伴提供一些思路和意见,如果对大家有些许作用是我的荣幸,文中有任何不妥和错误烦请大家不吝笔墨给我指出来,感激不尽。

下载

本文的下载主要针对NSURLSession展开,其他的下载方式比如使用NSData,本文应用不到,这里就不赘述了。

NSURLSession有2种下载模式
第一种:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]];
self.downloadSession = [NSURLSession sharedSession];
self.downloadTask = [self.downloadSession downloadTaskWithRequest:request];
[self.downloadTask resume];

第二种

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]];
NSURLSessionConfiguration *sessionCon = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:self.downloadUrl];
self.downloadSession = [NSURLSession sessionWithConfiguration:sessionCon delegate:self delegateQueue:[NSOperationQueue mainQueue]];
self.downloadTask = [self.downloadSession downloadTaskWithRequest:request];
[self.downloadTask resume];

2中方式的暂停下载和继续下载均可以使用

[self.downloadTask suspend];
[self.downloadTask resume];

当然暂停和继续还可以使用如下方式

[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    self.resumeData = resumeData;
}];
self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];

注意
看到这里一些小伙伴可能会有些疑惑,两种下载方式和两种暂停继续的方式有何却别,分别针对的是何种使用场景,改如何选择,别着急,下面的内容都会说明,这里暂且有个印象就可以了。

后台下载

众所周知,自从NSURLSession发布后,就可以轻松的实现后台下载了,代码如下:

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]];
NSURLSessionConfiguration *sessionCon = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:self.downloadUrl];
self.downloadSession = [NSURLSession sessionWithConfiguration:sessionCon delegate:self delegateQueue:[NSOperationQueue mainQueue]];
self.downloadTask = [self.downloadSession downloadTaskWithRequest:request];
[self.downloadTask resume];

注意
你没有看错,就是上面的第二种下载方式,这里也就是下载的2种方式的区别,第一种不支持后台下载,而第二种支持后台下载。

断点续传

适用于网络不中断、APP不重启、iOS9以及以前版本系统。

这里也有2种形式可以实现断点续传,在iOS9及以前的系统中区别并不大
第一种:

[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    self.resumeData = resumeData;
}];
self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];

第二种:

[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {

}];

在中断下载后可以直接在block中获取继续下载需要使用的resumeData,还可以到代理方法中获取

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
        if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]){
            self.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
            self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];
            [self.downloadTask resume];
    }
}

注意
1、在继续下载的时候,需要有一个NSData形式的resumeData数据实现继续下载,通过转换可以看出,resumeData本质上是一个XML文件,主要记录的是当前下载的链接、已经下载的数据大小、总数据大小等恢复下载需要的信息,如下:

如下是一个下载的resumeData的XML数据:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSURLSessionDownloadURL</key>
<string>http://sbslive.cnrmobile.com/storage/storage2/18/01/18/46eeb50b3f21325a6f4bd0e8ba4d2357.3gp</string>
<key>NSURLSessionResumeBytesReceived</key>
<integer>68188</integer>
<key>NSURLSessionResumeCurrentRequest</key>
<data>
YnBsaXN0MDDUAQIDBAUGeXpYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
AAGGoK8QGQcILEdNTlRVVlcrWDlZWmhpamtsbW5vcHVVJG51bGzfEB8JCgsMDQ4PEBES
ExQVFhcYGRobHB0eHyAhIiMkJSYnKCkpKywtLi8wMCkvNCspNjc4OTo7KSk+OykvQkMt
RVIkMV8QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzIwXxAgX19uc3VybHJl
cXVlc3RfcHJvdG9fcHJvcF9vYmpfMjFfEBBzdGFydFRpbWVvdXRUaW1lXxAecmVxdWly
ZXNTaG9ydENvbm5lY3Rpb25UaW1lb3V0XxAgX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
cF9vYmpfMTBWJGNsYXNzXxAgX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMTFf
ECBfX25zdXJscmVxdWVzdF9wcm90b19wcm9wX29ial8xMl8QIF9fbnN1cmxyZXF1ZXN0
X3Byb3RvX3Byb3Bfb2JqXzEzXxAaX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcHNfECBf
X25zdXJscmVxdWVzdF9wcm90b19wcm9wX29ial8xNF8QIF9fbnN1cmxyZXF1ZXN0X3By
b3RvX3Byb3Bfb2JqXzE1XxAacGF5bG9hZFRyYW5zbWlzc2lvblRpbWVvdXRfECBfX25z
dXJscmVxdWVzdF9wcm90b19wcm9wX29ial8xNl8QFGFsbG93ZWRQcm90b2NvbFR5cGVz
XxAgX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMTdfECBfX25zdXJscmVxdWVz
dF9wcm90b19wcm9wX29ial8xOFIkMF8QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bf
b2JqXzE5XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfOV8QH19fbnN1cmxy
ZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzhfEB9fX25zdXJscmVxdWVzdF9wcm90b19wcm9w
X29ial83XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfNl8QH19fbnN1cmxy
ZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzVfEB9fX25zdXJscmVxdWVzdF9wcm90b19wcm9w
X29ial80XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfM1IkMl8QH19fbnN1
cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzFfEB9fX25zdXJscmVxdWVzdF9wcm90b19w
cm9wX29ial8wXxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJvcF9vYmpfMhAJgACAACMA
AAAAAAAAAAiAAoAYgAeACoAKgACAB4ALgAAQAIAMgA0QAoAOgAiAAIAAgAmACIAAgAcQ
FoADgAKABgjTSA9JKUtMV05TLmJhc2VbTlMucmVsYXRpdmWAAIAFgARfEFtodHRwOi8v
c2JzbGl2ZS5jbnJtb2JpbGUuY29tL3N0b3JhZ2Uvc3RvcmFnZTIvMTgvMDEvMTgvNDZl
ZWI1MGIzZjIxMzI1YTZmNGJkMGU4YmE0ZDIzNTcuM2dw0k9QUVJaJGNsYXNzbmFtZVgk
Y2xhc3Nlc1VOU1VSTKJRU1hOU09iamVjdCNATgAAAAAAABAACRAEE///U0dF
VNNbXA9dYmdXTlMua2V5c1pOUy5vYmplY3RzpF5fYGGAD4AQgBGAEqRjZGVmgBOAFIAV
gBaAF1pVc2VyLUFnZW50VkFjY2VwdF8QD0FjY2VwdC1MYW5ndWFnZV8QD0FjY2VwdC1F
bmNvZGluZ18QL1pZTERvd25sb2FkZXIvMSBDRk5ldHdvcmsvODA4LjAuMiBEYXJ3aW4v
MTYuMC4wUyovKlVlbi11c11nemlwLCBkZWZsYXRl0k9QcXJfEBNOU011dGFibGVEaWN0
aW9uYXJ5o3N0U18QE05TTXV0YWJsZURpY3Rpb25hcnlcTlNEaWN0aW9uYXJ50k9Qdndc
TlNVUkxSZXF1ZXN0onhTXE5TVVJMUmVxdWVzdF8QD05TS2V5ZWRBcmNoaXZlctF7fF8Q
G05TS2V5ZWRBcmNoaXZlUm9vdE9iamVjdEtleYABAAgAEQAaACMALQAyADcAUwBZAJoA
nQDAAOMA9gEXAToBQQFkAYcBqgHHAeoCDQIqAk0CZAKHAqoCrQLQAvIDFAM2A1gDegOc
A74DwQPjBAUEJwQpBCsELQQ2BDcEOQQ7BD0EPwRBBEMERQRHBEkESwRNBE8EUQRTBFUE
VwRZBFsEXQRfBGEEYwRlBGcEaQRqBHEEeQSFBIcEiQSLBOkE7gT5BQIFCAULBRQFHQUf
BSAFIgUrBS8FNgU+BUkFTgVQBVIFVAVWBVsFXQVfBWEFYwVlBXAFdwWJBZsFzQXRBdcF
5QXqBgAGBAYaBicGLAY5BjwGSQZbBl4GfAAAAAAAAAIBAAAAAAAAAH0AAAAAAAAAAAAA
AAAAAAZ+
</data>
<key>NSURLSessionResumeEntityTag</key>
<string>"5534b35d-7c7be1"</string>
<key>NSURLSessionResumeInfoTempFileName</key>
<string>CFNetworkDownload_JhfLFD.tmp</string>
<key>NSURLSessionResumeInfoVersion</key>
<integer>2</integer>
<key>NSURLSessionResumeOriginalRequest</key>
<data>
YnBsaXN0MDDUAQIDBAUGUFFYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
AAGGoKwHCCQ7QUJISUojS0xVJG51bGzfEBkJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAh
IiMkJSYnKCgqJywjLS4vKionLyonNjclOVIkMV8QEHN0YXJ0VGltZW91dFRpbWVfEB5y
ZXF1aXJlc1Nob3J0Q29ubmVjdGlvblRpbWVvdXRfECBfX25zdXJscmVxdWVzdF9wcm90
b19wcm9wX29ial8xMFYkY2xhc3NfECBfX25zdXJscmVxdWVzdF9wcm90b19wcm9wX29i
al8xMV8QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzEyXxAgX19uc3VybHJl
cXVlc3RfcHJvdG9fcHJvcF9vYmpfMTNfEBpfX25zdXJscmVxdWVzdF9wcm90b19wcm9w
c18QIF9fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzE0XxAgX19uc3VybHJlcXVl
c3RfcHJvdG9fcHJvcF9vYmpfMTVfEBpwYXlsb2FkVHJhbnNtaXNzaW9uVGltZW91dF8Q
FGFsbG93ZWRQcm90b2NvbFR5cGVzUiQwXxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
cF9vYmpfOV8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzhfEB9fX25zdXJs
cmVxdWVzdF9wcm90b19wcm9wX29ial83XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
cF9vYmpfNl8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzVfEB9fX25zdXJs
cmVxdWVzdF9wcm90b19wcm9wX29ial80XxAfX19uc3VybHJlcXVlc3RfcHJvdG9fcHJv
cF9vYmpfM1IkMl8QH19fbnN1cmxyZXF1ZXN0X3Byb3RvX3Byb3Bfb2JqXzFfEB9fX25z
dXJscmVxdWVzdF9wcm90b19wcm9wX29ial8wXxAfX19uc3VybHJlcXVlc3RfcHJvdG9f
cHJvcF9vYmpfMhAJIwAAAAAAAAAACIACgAuAB4AJgAmAAIAHgAoQABACgAiAAIAAgAeA
CIAAgAcQEIADgAKABgjTPA09Kj9AV05TLmJhc2VbTlMucmVsYXRpdmWAAIAFgARfEFto
dHRwOi8vc2JzbGl2ZS5jbnJtb2JpbGUuY29tL3N0b3JhZ2Uvc3RvcmFnZTIvMTgvMDEv
MTgvNDZlZWI1MGIzZjIxMzI1YTZmNGJkMGU4YmE0ZDIzNTcuM2dw0kNERUZaJGNsYXNz
bmFtZVgkY2xhc3Nlc1VOU1VSTKJFR1hOU09iamVjdCNATgAAAAAAABAACRP/
/9JDRE1OXE5TVVJMUmVxdWVzdKJPR1xOU1VSTFJlcXVlc3RfEA9OU0tleWVkQXJjaGl2
ZXLRUlNfEBtOU0tleWVkQXJjaGl2ZVJvb3RPYmplY3RLZXmAAQAIABEAGgAjAC0AMgA3
AEQASgB/AIIAlQC2ANkA4AEDASYBSQFmAYkBrAHJAeAB4wIFAicCSQJrAo0CrwLRAtQC
9gMYAzoDPANFA0YDSANKA0wDTgNQA1IDVANWA1gDWgNcA14DYANiA2QDZgNoA2oDbANu
A3ADcQN4A4ADjAOOA5ADkgPwA/UEAAQJBA8EEgQbBCQEJgQnBDAENQRCBEUEUgRkBGcE
hQAAAAAAAAIBAAAAAAAAAFQAAAAAAAAAAAAAAAAAAASH
</data>
<key>NSURLSessionResumeServerDownloadDate</key>
<string>Mon, 20 Apr 2015 08:05:49 GMT<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值