NSURLSession的下载和断点继传以及后台下载功能

一.大体步骤

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

 1. 创建session指定其configuration
 2. 由session执行任务得到task
 3. task调用resume,启动网络请求

二.task分类

session的任务有四种:

 1. 数据任务 Data task
 2. 下载任务 Download task
 3. 上传任务 Upload task
 4. 流任务 Stream task ios9之后出现的,用于TCP/IP流

三. configuration类型

configuration的类型有三种:

 1. 默认配置 Default sessions:
    使用磁盘缓存,用将证书存在用户的钥匙串
 2. 及时配置 Ephemeral sessions:
    不使用磁盘缓存,也存储证书,它的信息存于RAM中,如果session被invalidate,这些信息也被清理掉
 3. 后台配置 Background sessions:
     配置上同默认配置,但是有一个独立进程来操作上传/下载

四.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的代理继承关系如图:
NSURLSessionDelegate
|
NSURLSessionTaskDelegate
| | |
NSURLSessionDataDelegate NSURLSessionDownloadDelegate NSURLSessionStreamDelegate

1. 数据任务 Data task

使用:

let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let url = NSURL(string:datadUrlNeighbor)
task.resume()

进入代理方法顺序:

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

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {

//这个方法,只有在sessiontaskdatatask的时候才会进入
 completionHandler(.Allow)

}

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

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
    print("did receive data")
    let dic = try? NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
    print("get dic:\(dic!)")
}

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

func URLSession(session: NSURLSession, task: NSURLSessionTask,  error: NSError?) {
    print("did complete")
}

2. 下载任务 Download task

使用:

let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config,delegate: self, delegateQueue: NSOperationQueue.mainQueue())

//方式一 :不使用handler 会进入代理方法
let url = NSURL(string:datadUrlNeighbor)
let downloadTask = session.downloadTaskWithURL(url!)
downloadTask.resume()

进入代理方法顺序:

1.NSURLSessionDownloadDelegate的didWriteData,数据正在写入沙盒的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的didFinishDownloadingToURL方法,当数据下载完成后,此时的文件是一个以tmp结尾的文件,命名类似于:
tmp文件

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

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
    print("download to url:\(location)")

    self.moveToCache(location, name: "ivy.zip")
    self.tintLabel.text = "download to url"
}

4.进入didCompleteWithError方法

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?){
}

三. 上传

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

四. 断点继传

使用:

注意:
1. 这里和下载的区别是:task设置为成员变量了,因为在中断的时候,需要这个task来调用cancelByProducingResumeData
2. session也设置为了成员变量,因为在继传的时候,需要用这个session来调用downloadTaskWithResumeData,开启一个新的downloadtask
//经测试这里写backgroundconfig和defaultconfig都可以
//let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(backgroundId)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()

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

let url = NSURL(string:downloadUrlNeighbor)
downloadSessionTask = downloadSession!.downloadTaskWithURL(url!)
downloadSessionTask!.resume()

顺序:

1. 按下中止按钮:

在回调块中,保存  self.resumeData,便于在继传时使用
downloadSessionTask?.cancelByProducingResumeData({ (data:NSData?) in
    self.resumeData = data
    self.downloadSessionTask = nil //downloadSessionTask已经没用了 要置为nil,因为下次继传时会由session新开一个task
})

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

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

1. 进入didWriteData,毕竟也是写了一些数据的
2. 会进cancelByProducingResumeData的回调,在回调里,我们要记录下resumedata,这是继传时要传入的参数,还要设置成员downloadSessionTask为nil,因为下次的继传会由session创建一个新的task,通过调用downloadTaskWithResumeData
3. 进入代理didCompleteWithError方法

2. 继传

比如我们用一个按钮来启动继传,其中的代码如下:
就是保存的成员变量task 调用downloadTaskWithResumeData方法

@IBAction func clickGoOn(sender: AnyObject) {
    guard self.resumeData != nil else{
        return
     }

     //这样写可以进入代理:1 didFinishDownloadingToURL 2 didCompleteWithError
    downloadSessionTask = downloadSession?.downloadTaskWithResumeData(self.resumeData!)
    downloadSessionTask?.resume()
}

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

进入代理方法的顺序:
1.didResumeAtOffset

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
    print("didresume:\(fileOffset),total:\(expectedTotalBytes)")
}

2.didWriteData 又开始写入磁盘了

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    print("didwrite:\(bytesWritten), \(totalBytesWritten), \(totalBytesExpectedToWrite)")
}

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

func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
    print("download to url:\(location)")

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

    self.tintLabel.text = "download to url"
    }

4.didCompleteWithError 这回是真的下载完成了

 func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?){
    print("did complete")
}

四. 后台下载

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

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

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

    3. session必须设置delegate

    4. 只支持HTTP/HTTPS模式

    5. 只支持从文件上传,不支持从data上传
(也就是只能用这个函数:func uploadTaskWithRequest(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里下载直到完成为止

使用:

let config = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(backgroundId)
let session = NSURLSession(configuration:config, delegate:self, delegateQueue: NSOperationQueue.mainQueue())
let url = NSURL(string:downloadUrlNeighbor)
let task = session.downloadTaskWithURL(url!)

task.resume()

进入代理顺序:
1.开始下载时
NSURLSessionDownloadDelegate代理的didWriteData方法

2.按下home,APP进入后台
代理的didWriteData方法不再被进入,而是程序后台静默下载

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 background activity.
    //这个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文件

第三步:进入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:打算翻译下那篇参考文档 含金量挺高的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值