阅读 313

Alamofire(二)URLSession

Alamofire学习(一)网络基础

Alamofire(二)URLSession

Alamofire(三)后台下载原理

@TOC

  • 最近在学习Alamofire框架,上一篇博客:Alamofire学习(一)网络基础 讲解了一些网络协议相关的知识,本篇博客主要是讲解一下Alamofire框架用到的IOS系统API URLSession.
  • 为什么要先学习URLSession

Alamofire中是使用URLSession进行封装的,所以有必要去先深入了解下URLSession. URLSession等同于NSURLSession,只是前者是Swift中的名字,后者是OC中的名字,他们之间是可以直接相互转换的。NSURLSession是同iOS7一同推出的,主要是对NSURLConnection进行了重构和优化,并且NSURLConnection在iOS 9的时候也已经被废弃,所以NSURLSession是NSURLConnection的取代者。

URLSession简介

Alamofire是一个为iOS和macOS打造的并基于Swift的网络库.它在Apple的基础网络架构上提供了更加优雅的接口来简化繁重而常用的网络请求任务。 Alamofire提供了链式的request/response方法,JSON的传参和响应序列化,身份认证和其他特性。Alamofire的优雅之处在于它完完全全是由Swift写成的,并且没有从它的Objective-C版本-AFNetworking那继承任何特性。

因为我们的Alamofire是对苹果URLSession的封装的网络框架,所以在探索Alamofire之前,我们除了需要掌握一些必备的网络相关知识外,还需要熟悉IOS的系统API URLSession

先来看一张概要图:

URLSession-kyl

URLSession类成员属性

1. URLSessionConfiguration

  • 3种会话模式
会话模式 特点 描述
默认会话模式(default) 工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。 默认模式,通常我们用这种模式就足够了。default模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书
瞬时会话模式(ephemeral) 该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。 系统没有任何持久性存储,所有内容的生命周期都与session相同,当session无效时,所有内容自动释放。
后台会话模式(background) 该模式在后台完成上传和下载,在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话。 background创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的会话。background模式与default模式非常相似,不过background模式会用一个独立线程来进行数据传输。background模式可以在程序挂起,退出,崩溃的情况下运行task。也可以利用标识符来恢复进。注意,后台Session一定要在创建的时候赋予一个唯一的identifier,这样在APP下次运行的时候,能够根据identifier来进行相关的区分。如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,IOS系统不会主动唤醒APP,只有用户下次启动了APP,数据传输才会继续

2. URLSessionTask

2. 1 URLSessionDataTask:数据

URLSessionDataTask: 处理从HTTP get请求中从服务器获取数据到内存中。

Use URLSession’s dataTask(with:) and related methods to create URLSessionDataTask instances. Data tasks request a resource, returning the server’s response as one or more NSData objects in memory. They are supported in default, ephemeral, and shared sessions, but are not supported in background sessions

2. 2 URLSessionUploadTask:上传

URLSessionUploadTask:上传硬盘中的文件到服务器,一般是HTTP POST 或 PUT方式

Use URLSession’s uploadTask(with:from:) and related methods to create URLSessionUploadTask instances. Upload tasks are like data tasks, except that they make it easier to provide a request body so you can upload data before retrieving the server’s response. Additionally, upload tasks are supported in background sessions.

2. 3 URLSessionDownloadTask:下载

URLSessionDownloadTask: 从远程服务器下载文件到临时文件位置

Use URLSession’s downloadTask(with:) and related methods to create URLSessionDownloadTask instances. Download tasks download a resource directly to a file on disk. Download tasks are supported in any type of session.

2. 4 URLSessionStreamTask:流

Use URLSession’s streamTask(withHostName:port:) or streamTask(with:) to create URLSessionStreamTask instances. Stream tasks establish a TCP/IP connection from a host name and port or a net service object.

3. URLSessionDelegate

4. 缓存策略 NSURLRequestCachePolicy

缓存策略 作用 说明
NSURLRequestUseProtocolCachePolicy = 0 默认缓存策略 如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取
NSURLRequestReloadIgnoringLocalCacheData = 1 URL应该加载源端数据,不使用本地缓存数据
NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4 本地缓存数据、代理和其他中介都要忽视他们的缓存,直接加载源数据
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData
NSURLRequestReturnCacheDataElseLoad = 2 指定已存的缓存数据应该用来响应请求,不管它的生命时长和过期时间。如果在缓存中没有已存数据来响应请求的话,数据从源端加载
NSURLRequestReturnCacheDataDontLoad = 3 指定已存的缓存数据用来满足请求,不管生命时长和过期时间。如果在缓存中没有已存数据来响应URL加载请求的话,不去尝试从源段加载数据,此时认为加载请求失败。这个常量指定了一个类似于离线模式的行为
NSURLRequestReloadRevalidatingCacheData = 5 指定如果已存的缓存数据被提供它的源段确认为有效则允许使用缓存数据响应请求,否则从源段加载数据。

5. URLSession 属性

属性类别 属性名称 作用
常规
常规 identifier 配置对象的后台会话标识符
常规 httpAdditionalHeaders 与请求一起发送的附加头文件的字典
常规 networkServiceType 网络服务的类型
常规 allowsCellularAccess 一个布尔值,用于确定是否应通过蜂窝网络进行连接
常规 timeoutIntervalForRequest 等待其他数据时使用的超时间隔
常规 timeoutIntervalForResource 资源请求应该允许的最大时间量
常规 sharedContainerIdentifier 应该下载后台URL会话中的文件的共享容器的标识符
常规 waitsForConnectivity 一个布尔值,指示会话是否应等待连接变为可用或者立即失败
设置Cookie政策
httpCookieAcceptPolicy 决定何时应该接受Cookie的策略常量
httpShouldSetCookies 一个布尔值,用于确定请求是否应包含来自Cookie存储的Cookie
httpCookieStorage 管理cookie存储的单一对象(共享实例)
HTTPCookie 表示HTTP cookie的对象。它是一个不可变的对象,从包含cookie属性的字典中初始化
设置安全策略
tlsMaximumSupportedProtocol 在此会话中进行连接时客户端应请求的最大TLS协议版本
tlsMinimumSupportedProtocol 协议协商期间应该接受的最小TLS协议
urlCredentialStorage 提供身份验证凭据的凭证存储
设置缓存策略
urlCache 用于向会话中的请求提供缓存响应的URL缓存
requestCachePolicy 一个预定义常量,用于确定何时从缓存中返回响应
支持后台转移
sessionSendsLaunchEvents 一个布尔值,指示在传输完成时是否应该在后台继续或启动应用程序
isDiscretionary 一个布尔值,用于确定是否可以根据系统的判断来调度后台任务以获得最佳性能
支持自定义协议
protocolClasses 在会话中处理请求的额外协议子类的数组
URLProtocol 一个NSURLProtocol对象处理加载协议特定的URL数据。在NSURLProtocol类本身是一个抽象类,可以为与特定URL方案的URL处理基础设施。您可以为您的应用支持的任何自定义协议或URL方案创建子类
支持多路径TCP
multipathServiceType 指定用于通过Wi-Fi和蜂窝接口传输数据的多路径TCP连接策略的服务类型
URLSessionConfiguration.MultipathServiceType 指定多路径TCP使用的服务类型的常量
设置HTTP策略和代理属性
httpMaximumConnectionsPerHost 同时连接到给定主机的最大数量
httpShouldUsePipelining 一个布尔值,用于确定会话是否应使用HTTP流水线
connectionProxyDictionary 包含有关在此会话中使用的代理信息的字典
支持连接变化
waitsForConnectivity 一个布尔值,指示会话是否应等待连接变为可用或者立即失败
属性类别 属性名称 作用

URLSession使用

URLSession使用流程

URLSession使用流程

URLSession上传

详情可以参考:苹果官方文档:Uploading Streams of Data

URLSession上传

func postsesssionUploadTask(){
        //1.创建会话对象
        let config:URLSessionConfiguration=URLSessionConfiguration.default
        let session:URLSession=URLSession.init(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
        //2.根据会话对象创建task
         let urlstr="\(BASEURL)"+LUNBOURL
        let urls: NSURL = NSURL(string: urlstr)!
        //3.创建可变的请求对象
        var request:URLRequest  = URLRequest(url: urls as URL)
        //4.修改请求方法为POST
        request.httpMethod = "POST"
        //5.设置请求体-----可以不设置,有默认的
        request.httpBody = "".data(using: String.Encoding.utf8)
        //6.根据会话对象创建一个Task(发送请求)
        /*
         第一个参数:请求对象
         第二个参数:completionHandler回调(请求完成【成功|失败】
         data:响应体信息(期望的数据)
         response:响应头信息,主要是对服务器端的描述
         error:错误信息,如果请求失败,则error有值
         upDta:要上传的二进制数据
         */
let images:UIImage=UIImage.init(named: "bannerhomeOne")!
        let upData:Data=UIImagePNGRepresentation(images)!
//通过data数据上传
        let upTask=session.uploadTask(with: request, from: upData) { (data, res, error) in
            //上传完毕后
            if error != nil{
                print(error)
            }else{
                let str = String(data: data!, encoding: String.Encoding.utf8)
                print("上传完毕:\(str)")
            }
        }
       upTask.resume()
}    

复制代码

URLSession下载

普通下载

  • (1)先开启一个下载task
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())

        let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
        
        session.downloadTask(with: url).resume()
复制代码
  • (2)设置代理,接受回调,保存文件
//下载完成之后就回调URLSessionDownloadDelegate代理
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("下载完成 - \(location)")
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
        print("下载进度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
    }
复制代码

后台下载

后台下载除了需要普通下载的必须步骤(1,2)外还需做如下处理

  • (3)开启后台下载权限
 //用于保存后台下载的completionHandler
  var backgroundSessionCompletionHandler: (() -> Void)?
  
  func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
      self.backgroundSessionCompletionHandler = completionHandler
  }
复制代码
  • (4)下载完成时,调用系统回调,更新屏幕
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
      print("后台任务下载回来")
      DispatchQueue.main.async {
          guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
          backgroundHandle()
      }
  }
复制代码

断点续传下载

  1. 定义好变量,并遵循NSURLSessionDownloadDelegate协议
    var downloadTask : NSURLSessionDownloadTask?
    var partialData : NSData?
    var session : NSURLSession?
    var request : NSMutableURLRequest?
复制代码
  1. 为每个按钮增加点击事件
    //开始下载
    @IBAction func onDownLoad(sender: AnyObject) {
        self.downloadFile()
    }
    //挂起下载
    @IBAction func onSuspend(sender: AnyObject) {
        if(self.downloadTask != nil)
        {
            //挂起下载任务,将下载好的数据进行保存
            self.downloadTask?.cancelByProducingResumeData({ (resumeData:NSData!) -> Void in
                self.partialData = resumeData
                self.downloadTask = nil
            })
        }
//        downloadTask!.suspend()
    }
    //恢复下载
    @IBAction func onResume(sender: AnyObject) {
        if(self.downloadTask == nil)
        {
            //判断是否又已下载数据,有的话就断点续传,没有就完全重新下载
        if(self.partialData != nil)
        {
            self.downloadTask = self.session?.downloadTaskWithResumeData(self.partialData!)
            }
        else{
            self.downloadTask = self.session?.downloadTaskWithRequest(self.request!)
            }
        }
        downloadTask!.resume()
    }
    //开始下载文件
    func downloadFile()
    {
        NSLog("正在下载")
        //创建URL
        var urlStr:NSString = NSString(string: "http://cdn.wall88.com/51a317b5ef36713194.jpg")
        urlStr = urlStr.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
        var url = NSURL(string: urlStr)!
        //创建请求
        request = NSMutableURLRequest(URL: url)
        //创建默认会话
        var sessionConfig : NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
        sessionConfig.timeoutIntervalForRequest = 20 //设置请求超时时间
        sessionConfig.allowsCellularAccess = true //是否允许蜂窝网络下载
        //创建会话
        session = NSURLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)//指定配置和代理
        downloadTask = session!.downloadTaskWithRequest(request!)
        downloadTask!.resume()
    }

复制代码
  1. 根据下载内容的大小更新进度条
    //设置页面状态
    func setUIStatus(totalBytesWritten : Int64,expectedToWrite totalBytesExpectedToWrite:Int64 )
    {
        //调用主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), {
            if(Int(totalBytesExpectedToWrite) != 0 && Int(totalBytesWritten) != 0)
            {
                //更新进度条
                self.ps.progress = Float(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
                if(totalBytesExpectedToWrite == totalBytesWritten)
                {
                    self.lbl_hint.text! = "下载完毕"
                    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
                    self.btn_download.enabled = true
                }
                else{
                    self.lbl_hint.text = "正在下载"
                    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
                }
            }
            }
        )
    }
复制代码
  1. 下载文件
    //任务完成,不管是否下载成功
    func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
        self.setUIStatus(0, expectedToWrite: 0)
        if(error != nil)
        {
            NSLog("error is:\(error!.localizedDescription)")
        }
    }
    
    //下载完成
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
        var error:NSError?
        var cachePath : NSString = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.CachesDirectory, NSSearchPathDomainMask.UserDomainMask, true).first as NSString
        var savePath = cachePath.stringByAppendingPathComponent(lbl_title.text!)
        NSLog("\(savePath)")
        var saveUrl : NSURL = NSURL(fileURLWithPath: savePath)!
        var defalutManager = NSFileManager.defaultManager()
        //判断文件是否存在,存在则删除
        if(defalutManager.fileExistsAtPath(savePath))
        {
            defalutManager.removeItemAtPath(savePath, error: &error)
        }
        //下载成功后,文件是保存在一个临时的目录中的,需要自己拷置到该文件的目录
        defalutManager.copyItemAtURL(location, toURL: saveUrl, error: &error)
        if(error != nil)
        {
        NSLog("\(error)")
        }
    }
    
    //下载中
    func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        self.setUIStatus(totalBytesWritten, expectedToWrite: totalBytesExpectedToWrite)
    }
复制代码

参考大神博客:juejin.im/post/684490…