NSURLSession的下载和断点继传和后台下载

一.概述

NSURLSession始于ios7.它具有访问接口,上传/下载数据,断点继传和后台下载等功能:其使用步骤:

1.创建session指定其configuration

2.由session执行任务得到task

3. task调用resume,启动网络请求

1.task分类

session的任务有四种:

1.数据任务Data task

2.下载任务Download task

3.上传任务Upload task

4.流任务Stream task ios9之后出现的,用于TCP/IP流

2. configuration类型

configuration的类型有三种:

1.默认配置Defaultsessions:

使用磁盘缓存,用将证书存在用户的钥匙串

2.即时配置Ephemeralsessions:

不使用磁盘缓存,也存储证书,它的信息存于RAM中,如果session被invalidate,这些信息也被清理掉

3.后台配置Background sessions:

配置上同默认配置,但是有一个独立进程来操作上传/下载

3.session生成task方式

对于生成每种task的方法,共有4种方式,举downloadTask为例子

1.用urlrequest请求:

public func downloadTaskWithRequest(request: NSURLRequest)->

NSURLSessionDownloadTask

2.用url请求:

public func downloadTaskWithURL(url: NSURL)-> NSURLSessionDownloadTask

3.带handler的URLRequest请求,注意,如果写了handler,就不会进入代理方法,即使设置了代理也没用:

public func downloadTaskWithRequest(request: NSURLRequest,

completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void)-> NSURLSessionDownloadTask

4.带handler的URL请求:

public func downloadTaskWithURL(url: NSURL, completionHandler:

(NSURL?, NSURLResponse?, NSError?) -> Void)-> NSURLSessionDownloadTask

二.task分类讲解

下面讲解每种task的具体使用方法

NSURLSession的代理继承关系如图:2-1


2-1


1.数据任务Data task

使用:

letconfig =NSURLSessionConfiguration.defaultSessionConfiguration()

leturl = NSURL(string:datadUrlNeighbor)

task.resume()

进入代理方法顺序:

1.首先进入NSURLSessionDataDelegate的URLSession:dataTask:didReceiveResponse:completionHandler方法.我们要手动在这里调用completionHandler(.Allow).系统才会继续进入下一步的代理方法,否则到此就结束了

func URLSession(session: NSURLSession, dataTask:

NSURLSessionDataTask, didReceiveResponse response: NSURLResponse,

completionHandler: (NSURLSessionResponseDisposition) -> Void){

//这个方法,只有在session的task是datatask的时候才会进入

completionHandler(.Allow)

}

2.然后进入URLSession:dataTask:didReceiveData:方法,获得json数据,可以做业务操作

funcURLSession(session:NSURLSession, dataTask:NSURLSessionDataTask, didReceiveDatadata:NSData) {

print("did receivedata")

let dic = try?NSJSONSerialization.JSONObjectWithData(data,

options: .MutableContainers)

print("get dic:\(dic!)")

}

3.然后进入URLSession:task:didCompleteWithError方法,它是NSURLSessionTaskDelegate的方法,表示请求结束了

funcURLSession(session:NSURLSession, task:NSURLSessionTask, didCompleteWithError

error:NSError?){

print("did complete")

}

2.下载任务Download task

使用:

letconfig =NSURLSessionConfiguration.defaultSessionConfiguration()

letsession = NSURLSession(configuration: config,delegate: self, delegateQueue:NSOperationQueue.mainQueue())

//方式一:不使用handler会进入代理方法

leturl = NSURL(string:datadUrlNeighbor)

letdownloadTask = session.downloadTaskWithURL(url!)

downloadTask.resume()

进入代理方法顺序:

1.NSURLSessionDownloadDelegate的URLSession:downloadTask:didWriteData:totalBytesWritten: totalBytesExpectedToWrite:

2.,数据正在写入沙盒的tmp文件夹,就会调用这个方法.它是分批次写入的,每写入一段数据就调用这个方法一次,所以会被多次调用

参数解释:

bytesWritten是本次写入的数据长度

totalBytesWritten是已经写在磁盘上的长度

totalBytesExpectedToWrite是数据本来的长度

func URLSession(session: NSURLSession,

downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64,

totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64){

print("didwrite:\(bytesWritten),\(totalBytesWritten),\(totalBytesWritten),\(totalBytesExpectedToWrite)")

}

2.NSURLSessionDownloadDelegate的URLSession:downloadTask:didFinishDownloadingToURL方法,当数据下载完成后,此时的文件是一个以tmp结尾的文件,命名类似于:


tmp下载临时文件


注意:进入这个方法didFinishDownloadingToURL.在这里必须执对tmp文件的转移处理,否则当除了这个方法后,tmp文件就被删除了.

func URLSession(session:NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURLlocation: NSURL) {

print("download to url:\(location)")

self.moveToCache(location, name:"ivy.zip")

self.tintLabel.text ="download to url"

}

3.进入URLSession:downloadTask:didCompleteWithError方法

func URLSession(session:

NSURLSession,task: NSURLSessionTask,didCompleteWithError error: NSError?){

}

3.上传

由于上传本人研究不深入,暂时不写,以后更新本节

三.断点继传

1.开始使用:

注意:

这里和前面下载的区别是:

1.task设置为成员变量了,因为在中断的时候,需要这个task来调用cancelByProducingResumeData:

2.session也设置为了成员变量,因为在继传的时候,需要用这个session来调用downloadTaskWithResumeData,开启一个新的downloadtask

//经测试这里写backgroundconfig和defaultconfig都可以

//let config =

NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(backgroundId)

letconfig =NSURLSessionConfiguration.defaultSessionConfiguration()

downloadSession =

NSURLSession(configuration: config,delegate:self, delegateQueue: NSOperationQueue.mainQueue())

leturl = NSURL(string:downloadUrlNeighbor)

downloadSessionTask =downloadSession!.downloadTaskWithURL(url!)

downloadSessionTask!.resume()

执行顺序:

2.按下中止按钮:

在回调块中,保存self.resumeData,便于在继传时使用

downloadSessionTask?.cancelByProducingResumeData({ (data:NSData?)in

self.resumeData=data

self.downloadSessionTask=nil//downloadSessionTask已经没用了要置为nil,因为下次继传时会由session新开一个task

})

如果不按下中止按钮,它进入代理方法的顺序和正常下载是完全一样的.即先进入didWriteData,然后进入didFinishDownloadingToURL,最后didCompleteWithError

如果按下中止按钮,会发生:

1.进入URLSession:downloadTask:didWriteData,毕竟也是写了一些数据的

2.会进cancelByProducingResumeData的回调块,在回调块里,我们要记录下resumedata,这是继传时要传入的参数,还要设置成员downloadSessionTask为nil,因为下次的继传会由session创建一个新的task,通过调用downloadTaskWithResumeData

3.进入代理URLSession:task:didCompleteWithError方法

3.继传

比如我们用一个按钮来启动继传,其中的代码如下:

就是保存的成员变量task调用downloadTaskWithResumeData:方法

@IBAction func clickGoOn(sender:AnyObject) {

guardself.resumeData!=nilelse{

return

}

//这样写可以进入代理:1 didFinishDownloadingToURL 2 didCompleteWithError

downloadSessionTask = downloadSession?.downloadTaskWithResumeData(self.resumeData!)

downloadSessionTask?.resume()

}

由于downloadTaskWithResumeData也有2种方式,带handler和不带handler的,如果调用了带有handler的那个,则不进入代理方法

进入代理方法的顺序:

1.URLSession:downloadTask:didResumeAtOffset

func URLSession(session:NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffsetfileOffset: Int64, expectedTotalBytes: Int64) {

print("didresume:\(fileOffset),total:\(expectedTotalBytes)")

}

2.URLSession:downloadTask:didWriteData又开始写入磁盘了

func URLSession(session:

NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData

bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite:

Int64){

print("didwrite:\(bytesWritten),\(totalBytesWritten),\(totalBytesExpectedToWrite)")

}

3.URLSession:downloadTask:didFinishDownloadingToURL这个比较重要,一次下载可以多次中止,但是只有全部写入成功后才会进入这个代理,在这里将下载的文件转移走,不然出了这个方法会被删除的

func URLSession(session:NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURLlocation: NSURL) {

print("download to url:\(location)")

self.resumeUrl = location

self.moveToCache(self.resumeUrl,

name:"ivy.zip")

self.tintLabel.text ="download to url"

}

4.进入URLSession:task:didCompleteWithError这回是真的下载完成了

func URLSession(session: NSURLSession, task:NSURLSessionTask, didCompleteWithErrorerror: NSError?){

print("did complete")

}

四.后台下载

在下载过程中,如果按home键将app切换到后台,只要不杀死程序,session还能保持其下载能力,完成后,通知到appdelegate的handleEventsForBackgroundURLSession方法.,虽然app不会因此回到前端.

PS:据我所知,ios只允许后台程序活跃10分钟,(除了音频,电话,地图除外),而且还不定是连续的10分钟,那么,NSURLSession的后台下载是否包含在这10分钟内呢?我不是很好做测试啊.

注意:

1.后台config必须使用backgroundSessionConfigurationWithIdentifier,要传入一个唯一的标识符

2.有几个下载任务就要创建几个config和session.每个任务都需要一个独立标示的config,以及session

3. session必须设置delegate

4.只支持HTTP/HTTPS模式

5.只支持从文件上传,不支持从data上传

(也就是只能用这个函数:funcuploadTaskWithRequest(request: NSURLRequest, fromFile fileURL: NSURL,completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) ->NSURLSessionUploadTask

而不能用这个函数:func uploadTaskWithRequest(request: NSURLRequest,fromFile fileURL: NSURL, completionHandler: (NSData?, NSURLResponse?, NSError?)-> Void) -> NSURLSessionUploadTask)

6.请用真机调试,模拟器按下home键之后不会有效果,而是会一直在delegate里下载直到完成为止

使用:

letconfig =NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(backgroundId)

letsession = NSURLSession(configuration:config,

delegate:self, delegateQueue:NSOperationQueue.mainQueue())

leturl = NSURL(string:downloadUrlNeighbor)

lettask=session.downloadTaskWithURL(url!)

task.resume()

进入代理顺序:

1.开始下载时

进入NSURLSessionDownloadDelegate代理的.URLSession:downloadTaskdidWriteData方法

2.按下home,APP进入后台

代理的.URLSession:downloadTaskdidWriteData方法不再被进入,而是程序后台静默下载

3.下载完成后

第一步:进入AppDelegate的方法:

func application(application: UIApplication,

handleEventsForBackgroundURLSession identifier: String, completionHandler: ()

-> Void){

//self.downloadCompletionHandler = completionHandler

print("----application hadle

event")

let config = NSURLSessionConfiguration.backgroundSessionConfiguration(identifier)

//The new session is automatically reassociated with ongoing backgroundactivity.

//这个session被自动绑定到了后台运行的app

let session = NSURLSession(configuration: config , delegate:self.mySessionDelegate,

delegateQueue:NSOperationQueue.mainQueue())

//去使用的类里面注册一个handler,直接传过去也可以的,其实self.window.rootViewController = xxx也可以的

self.mySessionDelegate.addCompletionHandler(completionHandler, identifier:

identifier)

}

解释:

1.保存handler的方式是多种的,可以给AppDelegate设置一个dictionary的属性.也可以传入session的delegate的dictionary属性,我选择了后者

2.要创建一个session,文档说这个session会被自动绑定到后台的activity

第二步:

进入NSURLSessionDownloadDelegate的didFinishDownloadingToURL方法,请在这里搬运下载好的tmp文件

第三步:进入URLSession:task:didCompleteWithError

第四步:进入NSURLSessionDelegate的URLSessionDidFinishEventsForBackgroundURLSession方法

func URLSessionDidFinishEventsForBackgroundURLSession(session:NSURLSession)

{

print("did

finish events")

if (session.configuration.identifier!= nil) {

let handler = self.completionHandlerDictionary![session.configuration.identifier!]

guard handler != nil else {

return

}

handler!()

//移除dictionary中的数据

self.completionHandlerDictionary?.removeValueForKey(session.configuration.identifier!)

self.tintLabel.text="finish event"

}

}

在第三步或第四步里面,把从appdelegate里面获取到的handler执行一下,这么做的目的,文档告诉我们是为了让操作系统知道程序可以被继续安全的挂起.

参考文档:

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW44

demo:https://github.com/ivychenyucong/TestNSURLSession

ps:打算翻译下那篇参考文档含金量挺高的

推荐阅读更多精彩内容