iOS

【iOS开发】URLSession简介 & 大文件下载断

2017-02-20  本文已影响1617人  Lebron_James

首先介绍下这个Demo:点击开始下载后,开始下载一张图片;可以暂停,然后继续下载;上面可以显示下载进度;下载完成后,把下面的图片替换成我们下载的图片。

界面

为了实现下载功能,这里用到的一个主要类URLSession,下面首先介绍一下这个类。

URLSession

URLSession类原生支持datafileftphttphttps协议。跟它比较相关的另外一个类是URLSessionConfiguration,这个类可以用来设置会话的相关配置,在初始化会话实例时,有可能会用到。

URLSession类的层次结构

URLSession类的层次结构如下:

URLSessionAPI还提供了以下代理协议
初始化URLSession实例

在初始化一个URLSession的实例时,可以有四种情况,每一种情况处理不同的请求:

URLSession的使用步骤

任务开始之后,会话将会调用以下代理方法:

  1. 最初与服务器握手需要一个连接认证,例如SSL客户端证书,会话调用urlSession(_:task:didReceive:completionHandler:)或者urlSession(_:didReceive:completionHandler:)代理方法。
  2. 如果任务数据使用一个流提供的,调用urlSession(_:task:needNewBodyStream:)方法来获取一个InputStream对象,以为新的请求提供数据。
  3. 在最初传数据给服务器时,代理将会周期性地收到urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)回调,在这个回调中,描述了上传过程。
  4. 服务器发回响应
  5. 如果响应里面要求需要认证,会话调用urlSession(_:task:didReceive:completionHandler:)。回到第二步。
  6. 如果响应是一个HTTP回调响应,会话调用urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)。在这个代理方法里面,使用已提供的URLRequest对象、一个新的URLRequest对象(用于回调一个不同的URL)或者nil(把回调的响应体最为一个有效的响应并且把它返回最为结果)来执行completionHandler
  1. 对于使用downloadTask(withResumeData:)或者downloadTask(withResumeData:completionHandler:)创建的下载任务,会调用urlSession(_:downloadTask:didResumeAtOffset:expectedTotalBytes:)
  2. 对于一个数据任务,会调用urlSession(_:dataTask:didReceive:completionHandler:)。决定是否把数据任务转换为下载任务,然后调用completionHandler来继续接收数据或者下载数据。
  1. 在从服务器传输过程中,代理会周期性的收到任务级别的回调来报告传输的过程。
  1. 对于一个数据任务,会话可能会调用urlSession(_:dataTask:willCacheResponse:completionHandler:)。在这里,你要决定是否允许缓存。如果没有实现这个方法,那么将会默认使用会话配置对象指定的缓存策略来执行。
  2. 如果响应是多重编码的,会话可能会调用多次didReceiveResponse方法。
  3. 如果一个下载任务完成,会话调用urlSession(_:downloadTask:didFinishDownloadingTo:),这个方法中有一个文件临时存放的位置。我们需要在这里使用这些数据或者把数据保存到一个永久的位置。
  4. 当任务完成时,会话调用urlSession(_:task:didCompleteWithError:),这个方法中有一个可选的error,如果errornil,那么意味着文件已经下载完成。如果一个下载任务被用户暂停,下载任务可以继续,并且error不为nil,这个error中包含了一个userInfo字典,NSURLSessionDownloadTaskResumeData键对应的值就是目前已经下载的数据,我要要保存起来供继续下载使用。调用会话的downloadTask(withResumeData:)或者downloadTask(withResumeData:completionHandler:)方法新建一个下载任务,继续下载未完成的内容。如果这个任务不能继续,那么需要重新新建一个下载任务,重新下载。

代码演示

用一个结构来存储这个控制器要用到的常量:

private struct Constants {
    static let kDownload = "Download"
    static let kStartDownload = "开始下载"
    static let kPauseDownload = "暂停下载"
    static let kResumeDownload = "继续下载"
    static let kCompleteDownload = "下载完成"
}

点击下载按钮后,根据按钮的标题做相应的操作:

    @IBAction func startDownload(_ button: UIButton) {
        switch button.currentTitle! {
            
        case Constants.kStartDownload:
            currentTitle = Constants.kPauseDownload
            UIApplication.shared.isNetworkActivityIndicatorVisible = true
            
            // 创建会话相关配置
            let config = URLSessionConfiguration.background(withIdentifier: Constants.kDownload)
            // 在应用进入后台时,让系统决定决定是否在后台继续下载。如果是false,进入后台将暂停下载
            config.isDiscretionary = true
            
            // 创建一个可以在后台下载的session (其实会话的类型有四种形式)
            session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
            task = session.downloadTask(with: request)
            task.resume()
            
        case Constants.kPauseDownload:
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
            currentTitle = Constants.kResumeDownload
            
            // 保存已经下载的位置
            task.cancel { (data) in
                self.resumeData = data
            }
            
        case Constants.kResumeDownload:
            UIApplication.shared.isNetworkActivityIndicatorVisible = true
            currentTitle = Constants.kPauseDownload
            
            // 重新建立一个下载任务,继续下载未完成的数据
            task = session.downloadTask(withResumeData: resumeData)
            task.resume()
            
        case Constants.kCompleteDownload:
            print("kCompleteDownload-----下载完成")
            
        default:
            break
        }
    }

下面是实现URLSessionDownloadDelegate的代理方法:

extension ViewController: URLSessionDownloadDelegate {
    
    // 每下载完一部分调用,可能会调用多次
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        progressView.progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        
        print(progressView.progress)
    }
    
    // 下载完成后调用
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        // 下载完成后,保存到缓存目录
        let destination = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last! + "/" + downloadTask.response!.suggestedFilename!
        do {
            try FileManager.default.moveItem(atPath: location.path, toPath: destination)
        }
        catch {
            print(error.localizedDescription)
        }
        
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
        imageView.image = UIImage(contentsOfFile: destination)
        currentTitle = Constants.kCompleteDownload
        // 在后台下载完成,重新进入前台,把progress设置为1.0;如果不设置,progress的值是进入后台之前的值
        progressView.progress = 1.0
        session.invalidateAndCancel() // 下载完成,使session失效
    }
    
    // 任务完成时调用,但是不一定下载完成;用户点击暂停后,也会调用这个方法
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            // 如果下载任务可以恢复,那么NSError的userInfo包含了NSURLSessionDownloadTaskResumeData键对应的数据,保存起来,继续下载要用到
            if let data = (error as! NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
                resumeData =  data
            }
        }
    }
    
    // 继续下载时调用
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
        print("didResumeAtOffset-----继续下载")
    }
}

关键代码都在这里,大家可以下载Demo。有什么问题,欢迎留言。谢谢!

Demo地址 >>

如果文中有错误,请指出!我们共同学习,共同进步。谢谢!

上一篇 下一篇

猜你喜欢

热点阅读