URLSession 下載數(shù)據(jù)

URLSession是一個(gè)完整的網(wǎng)絡(luò)API,用于通過(guò)HTTP上傳和下載內(nèi)容哥牍。

URL loading system包括加載URL的類(lèi)以及許多重要的幫助程序類(lèi)毕泌,這些輔助程序類(lèi)與這些URL loading 一起使用以修改其行為。主要幫助程序類(lèi)分為五類(lèi):協(xié)議支持嗅辣,身份驗(yàn)證和憑據(jù)撼泛,cookie存儲(chǔ),配置管理和緩存管理辩诞。
簡(jiǎn)單來(lái)說(shuō)URL loading system 就是一組用于與服務(wù)器通信的類(lèi).下圖有點(diǎn)老了是2013年的版本, 最新的版本沒(méi)找到.


URL loading system

使用URLSessionAPI坎弯,您的應(yīng)用會(huì)創(chuàng)建一個(gè)或多個(gè)會(huì)話(huà),每個(gè)會(huì)話(huà)都會(huì)協(xié)調(diào)一組相關(guān)的數(shù)據(jù)傳輸任務(wù)译暂。例如抠忘,你可以創(chuàng)建多個(gè)會(huì)話(huà), 一個(gè)可以用于下載數(shù)據(jù), 一個(gè)用于請(qǐng)求數(shù)據(jù), 在每個(gè)會(huì)話(huà)中,您的應(yīng)用程序會(huì)添加一系列任務(wù)外永,每個(gè)任務(wù)都代表對(duì)特定URL的請(qǐng)求崎脉。

URL會(huì)話(huà)的類(lèi)型

URLSession是負(fù)責(zé)發(fā)送和接收HTTP請(qǐng)求的關(guān)鍵對(duì)象, URLSession具有shared基本請(qǐng)求的單例會(huì)話(huà)(沒(méi)有配置對(duì)象)。創(chuàng)建的會(huì)話(huà)不可自定義. 對(duì)于其他類(lèi)型的會(huì)話(huà)伯顶。通過(guò)URLSessionConfiguration創(chuàng)建囚灼,有三種形式:

.default:默認(rèn)會(huì)話(huà)的行為與shared會(huì)話(huà)非常相似(除非您進(jìn)一步自定義它們),但是他可以讓您使用委托增量方式獲取數(shù)據(jù)祭衩。您可以通過(guò)調(diào)用URLSessionConfiguration類(lèi)上的默認(rèn)方法來(lái)創(chuàng)建默認(rèn)會(huì)話(huà)配置灶体。
shared 是無(wú)法設(shè)置委托的, 所以只能使用系統(tǒng)提供的閉包處理數(shù)據(jù)

open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

但是default會(huì)話(huà)是可以自定義委托對(duì)象, 使用委托回調(diào)來(lái)處理數(shù)據(jù), 這里一定要注意以增量的方式獲取數(shù)據(jù), 這點(diǎn)與閉包區(qū)別很大, 下面會(huì)具體講到

.ephemeral:臨時(shí)會(huì)話(huà)與默認(rèn)會(huì)話(huà)類(lèi)似,但它們不會(huì)將高速緩存掐暮,cookie或憑據(jù)寫(xiě)入磁盤(pán)蝎抽。您可以通過(guò)調(diào)用URLSessionConfiguration類(lèi)上的臨時(shí)方法來(lái)創(chuàng)建臨時(shí)會(huì)話(huà)配置。
.background:允許會(huì)話(huà)在后臺(tái)執(zhí)行上載或下載任務(wù)路克。即使應(yīng)用程序本身被系統(tǒng)暫驼两幔或終止养交,傳輸仍會(huì)繼續(xù)。(這個(gè)很牛呀)

URLSessionConfiguration是一個(gè)配置會(huì)話(huà)的類(lèi), 例如, 請(qǐng)求的超時(shí)時(shí)長(zhǎng), 緩存策略, 請(qǐng)求頭等等

URLSessionTask是一個(gè)表示任務(wù)對(duì)象的抽象類(lèi), 抽象類(lèi)是不能直接創(chuàng)建對(duì)象來(lái)使用的, 須有其子類(lèi)來(lái)完成, 子類(lèi)會(huì)話(huà)創(chuàng)建一個(gè)或多個(gè)任務(wù)來(lái)執(zhí)行獲取數(shù)據(jù)和下載或上載文件的事件瓢宦。

有三種類(lèi)型的具體會(huì)話(huà)任務(wù):

URLSessionDataTask:將此任務(wù)用于HTTP GET請(qǐng)求碎连,以將數(shù)據(jù)從服務(wù)器檢索到內(nèi)存。
URLSessionUploadTask:使用此任務(wù)通常通過(guò) POST或PUT方法將文件從磁盤(pán)上傳到Web服務(wù)驮履。
URLSessionDownloadTask:使用此任務(wù)將文件從遠(yuǎn)程服務(wù)下載到臨時(shí)文件位置鱼辙。

具體的工作流程首先創(chuàng)建一個(gè)會(huì)話(huà)URLSession, 然后使用URLSessionConfiguration來(lái)配置會(huì)話(huà), 然后使用URLSession去調(diào)用task方法, 調(diào)用不同的task會(huì)返回對(duì)應(yīng)的task任務(wù), 然后由task執(zhí)行resume()具體的任務(wù).

每一個(gè)App里面不可能只存在一個(gè)網(wǎng)絡(luò)請(qǐng)求, 存在多個(gè)網(wǎng)絡(luò)請(qǐng)求, 如果這些網(wǎng)絡(luò)請(qǐng)求的配置(URLSessionConfiguration)是相同的, 你不應(yīng)該為每個(gè)請(qǐng)求創(chuàng)建一個(gè)URLSession, 而是創(chuàng)建一個(gè)URLSession單例對(duì)象, 在他們之間共享.


上面說(shuō)到URLSessionTask有三種任務(wù), 數(shù)據(jù), 上傳和下載任務(wù)

數(shù)據(jù)任務(wù)

獲取數(shù)據(jù)有兩種方式一種使用閉包(Receive Results with a Completion Handler), 一種使用委托(Receive Transfer Details and Results with a Delegate)

1. 使用閉包

獲取數(shù)據(jù)的最簡(jiǎn)單方法是創(chuàng)建使用閉包處理程序的數(shù)據(jù)任務(wù)。通過(guò)這種方式, 不需要配置會(huì)話(huà), 直接調(diào)用共享會(huì)話(huà)URLSession.shared玫镐,任務(wù)將服務(wù)器的響應(yīng)座每,數(shù)據(jù)和可能的錯(cuò)誤傳遞給您提供的閉包。最關(guān)鍵的是數(shù)據(jù)不是增量返回的, 是一次完整的返回, 不需要自己去處理增量數(shù)據(jù).

創(chuàng)建完成處理程序以接收任務(wù)的結(jié)果
        let session = URLSession.shared
        let sessionTask = session.dataTask(with: request) { (data, response, error) in
            if let error = error {
                print(error as Any)
                return
            }
            guard let httpResponse = response as? HTTPURLResponse,
                (200...299).contains(httpResponse.statusCode) else {
                    print(response as Any)
                    return
            }
            if let mimeType = httpResponse.mimeType, mimeType == "application/json",
                let data = data {
                DispatchQueue.main.async {
                    let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
                    print(json as Any)
                }
                
            }
        }
        sessionTask.resume()
  1. 驗(yàn)證error參數(shù)是否nil摘悴。如果不是,則發(fā)生傳輸錯(cuò)誤; 處理錯(cuò)誤并退出舰绘。
  2. 檢查response參數(shù)以驗(yàn)證狀態(tài)代碼是否指示成功蹂喻,以及MIME類(lèi)型是否為預(yù)期值。如果沒(méi)有捂寿,請(qǐng)?zhí)幚矸?wù)器錯(cuò)誤并退出口四。
    mimeType代表服務(wù)器給我們返回的數(shù)據(jù)類(lèi)型, 根據(jù)這個(gè)字段來(lái)區(qū)分如何去解析數(shù)據(jù), 如application/json表示JSON數(shù)據(jù), text/html表示HTML數(shù)據(jù)等等
  3. data我們需要的數(shù)據(jù), dataTask是異步任務(wù), 一半獲取數(shù)據(jù)之后如果要進(jìn)行在主線(xiàn)程刷新UI, 這時(shí)需要回到主線(xiàn)程.
  4. 創(chuàng)建的task任務(wù), 是處于掛起狀態(tài)的, 他自己不會(huì)自動(dòng)執(zhí)行請(qǐng)求數(shù)據(jù)的任務(wù), 需要手動(dòng)執(zhí)行resume()
    使用閉包處理有兩種方法, 如下所示
open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask

open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
2. 使用代理

共享URLSession雖然使用起來(lái)簡(jiǎn)單方便, 但是存在很多局限, 如果想使用更加完善的請(qǐng)求, 則可以使用代理.

實(shí)現(xiàn)委托以接收任務(wù)的結(jié)果

通過(guò)代理方法,數(shù)據(jù)是分批依次返回的秦陋,直到傳輸完成或失敗出現(xiàn)錯(cuò)誤蔓彩。隨著數(shù)據(jù)傳輸?shù)倪M(jìn)行,URLSessionDataDelegate會(huì)收到代理事件urlSession(_:dataTask:didReceive:)

在使用閉包處理時(shí), 使用和系統(tǒng)給我們提供的單例對(duì)象shared, 我們自己創(chuàng)建時(shí), 也應(yīng)該使用單例對(duì)象, 因?yàn)閁RLSession在請(qǐng)求數(shù)據(jù)沒(méi)有完成之前不能被釋放掉, 否則, 數(shù)據(jù)將不能被請(qǐng)求
如下所示

 override func viewDidLoad() {
        super.viewDidLoad()
        let sessionConfigure = URLSessionConfiguration.default
        sessionConfigure.networkServiceType = .default
        let session = URLSession.init(configuration: sessionConfigure, delegate: self, delegateQueue: OperationQueue.main)
        let sessionTask = session.dataTask(with: request)
    }

這樣URLSession會(huì)在viewDidLoad() 方法執(zhí)行完畢之后就會(huì)被釋放掉, 您將不會(huì)受到代理的回調(diào)數(shù)據(jù). (這是一個(gè)巨坑, 這個(gè)和AVPlayer一樣, AVPlayer如果被釋放了, 音視頻也將不能播放, 使用時(shí)要注意)

創(chuàng)建使用委托的URLSession

var receiveData:Data?

private lazy var session: URLSession = {
    let configuration = URLSessionConfiguration.default
    configuration.waitsForConnectivity = true
    return URLSession(configuration: configuration,
                      delegate: self, delegateQueue: nil)
}()

func delegateHandler(request:URLRequest) {
        let sessionTask = session.dataTask(with: request)
        receiveData = Data()
        sessionTask.resume()
}

receiveData用來(lái)拼接每次請(qǐng)求獲取的數(shù)據(jù), 懶加載session, 如果多次創(chuàng)建只會(huì)存在一個(gè). delegateHandler 用來(lái)發(fā)起請(qǐng)求

URLSessionDataDelegate代理方法

  // 1
   func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        guard let response = response as? HTTPURLResponse,
            (200...299).contains(response.statusCode),
            let mimeType = response.mimeType,
            mimeType == "application/json" else {
                completionHandler(.cancel)
                return
        }
        completionHandler(URLSession.ResponseDisposition.allow)
    }
    //2
   func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        print("receive = \(data.count)")
        receiveData?.append(data)
    }
    //3
   func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            print("fail")
        }else {
            if let data = receiveData {
                let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
                print(json as Any)
            }
            print("success")
        }
    }
  1. 收到響應(yīng)的回調(diào), 在該代理方法中可以進(jìn)行一些數(shù)據(jù)驗(yàn)證, 決定請(qǐng)求時(shí)繼續(xù)還是取消, 或者其他的操作
  2. 依次獲取的數(shù)據(jù)
  3. 請(qǐng)求結(jié)束或者出現(xiàn)錯(cuò)誤的回調(diào)

下載任務(wù)

URLSessionDataTaskURLSessionDownloadTask都可以用來(lái)實(shí)現(xiàn)下載任務(wù), 在上面使用代理的方式請(qǐng)求數(shù)據(jù)的時(shí)候, 代理方法
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
以增量的方式來(lái)獲取數(shù)據(jù), 我們下載文件的時(shí)候也可以使用這種方法.

以下載mp3文件為例

1. URLSessionDataTask下載方式
let url = URL.init(string: "")
let request = URLRequest.init(url: url!)
let sessionTask = session.dataTask(with: request)
receiveData = Data()
sessionTask.resume()

前面實(shí)現(xiàn)URLSessionDataDelegate的代理方法, 是用來(lái)接收J(rèn)SON數(shù)據(jù)的, 既然我們要下載mp3文件, 就要對(duì)代理修改, 適應(yīng)mp3 文件的下載
首先要?jiǎng)?chuàng)建一個(gè)下載路徑

    var downloadPath = ""

      if let documentsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first {
            print(documentsPath)
            downloadPath = documentsPath + "/download"
            do {
                try FileManager.default.createDirectory(atPath: downloadPath, withIntermediateDirectories: true, attributes: nil)
            } catch {
                print(error)
            }
        }
// URLSessionDataDelegate 代理方法
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        guard let response = response as? HTTPURLResponse,
            (200...299).contains(response.statusCode),
            let mimeType = response.mimeType,
            mimeType == "audio/mpeg" else { // 1. 
                completionHandler(.cancel)
                return
        }
        completionHandler(URLSession.ResponseDisposition.allow)
    }
    
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        print("receive = \(data.count)")
        receiveData?.append(data)
        print("當(dāng)前下載 = \(data.count), 已經(jīng)下載 = \(dataTask.countOfBytesReceived), 總共需要下載 = \(dataTask.countOfBytesExpectedToReceive)")

    }
    
    // URLSessionDownloadDelegate 下載完成也會(huì)走這個(gè)方法,后走
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            print("fail")
        }else {
            if let data = receiveData { // 2. 
                do {
                    // 如果名稱(chēng)相同, 會(huì)覆蓋之前的數(shù)據(jù)
                    let url = URL.init(fileURLWithPath: downloadPath + "/\(Date().description).mp3")
                    try data.write(to: url, options: Data.WritingOptions.atomicWrite)
                } catch let error as NSError {
                    print(error.localizedDescription)
                }
            }
            print("success")
        }
    }
  1. mimeType的類(lèi)型要修改成audio/mpeg
  2. 當(dāng)數(shù)據(jù)下載完成時(shí), 要把數(shù)據(jù)寫(xiě)入磁盤(pán)
  3. 需要注意的是, 當(dāng)寫(xiě)入文件是, 如果名字相同, 后者會(huì)覆蓋前者的文件
    并不是所有的文件都支持下載的, 這個(gè)要具體需要服務(wù)器來(lái)配置, 打印URLResponse, 會(huì)看到下面的內(nèi)容
<NSHTTPURLResponse: 0x60000243ba20> { URL: http://.mp3 } { Status Code: 200, Headers {
    "Accept-Ranges" =     (
        bytes
    );
    "Content-Length" =     (
        6502067
    );
    "Content-Type" =     (
        "audio/mpeg"
    );
    Date =     (
        "Wed, 26 Dec 2018 14:59:59 GMT"
    );
    Etag =     (
        "\"8078c31b864ce1:0\""
    );
    "Last-Modified" =     (
        "Sun, 09 Jun 2013 02:22:29 GMT"
    );
    Server =     (
        "Microsoft-IIS/7.5"
    );
    "X-Powered-By" =     (
        "ASP.NET"
    );
} }
  1. Accept-Ranges 代表是否支持?jǐn)帱c(diǎn)下載 bytes支持, none不支持
  2. Content-Length文件大小驳概,以字節(jié)為單位的十進(jìn)制數(shù), 對(duì)應(yīng)的expectedContentLength
  3. Content-Type文件類(lèi)型, 對(duì)應(yīng)的mimeType

一般的文件下載如果有一個(gè)下載進(jìn)度條會(huì)更好, 但是URLSessionDataDelegate沒(méi)有提供給我們進(jìn)度更新的代理方法, 這個(gè)只能有我們自己去處理, 要計(jì)算進(jìn)度需要三個(gè)值, 當(dāng)前下載的字節(jié)數(shù), 已經(jīng)下載的字節(jié)數(shù), 期望下載的字節(jié)數(shù),

  1. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)的代理方法返回了每次請(qǐng)求到的數(shù)據(jù)
  2. 在響應(yīng)頭里既然已經(jīng)返回了文件的大小, 可以通過(guò)expectedContentLength來(lái)獲取期望下載的數(shù)據(jù)大小, 也可以通過(guò)URLSessionTask的countOfBytesExpectedToReceive獲取
  3. 已經(jīng)下載的字節(jié)數(shù), 可使用累加每次下載的字節(jié)數(shù)來(lái)獲取, 也可以通過(guò)URLSessionTask 的countOfBytesReceived獲取
    如此, 可完成一個(gè)進(jìn)度條的功能
2. URLSessionDownloadTask下載方式

雖然使用URLSessionDataTask也完成了下載, 但是需要我們自己去處理的事情有點(diǎn)多, 而URLSessionDownloadTask則是Apple封裝的用于下載的任務(wù), 包括斷點(diǎn)下載都做了很好的封裝處理, 下面使用URLSessionDownloadTask 來(lái)完成同樣的mp3文件的下載

創(chuàng)建一個(gè)URLSessionDownloadTask任務(wù)

        let sessionTask = session.downloadTask(with: request)

實(shí)現(xiàn)URLSessionDownloadDelegate

   // 1
   func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print(location)
        do {
            try FileManager.default.moveItem(at: location, to: URL.init(fileURLWithPath: downloadPath + "/\(Date().description).mp3"))
        } catch let error as NSError {
            print(error)
        }
    }
    //2
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        print("當(dāng)前下載 = \(bytesWritten), 已經(jīng)下載 = \(totalBytesWritten), 總共需要下載 = \(totalBytesExpectedToWrite)")
        print("\(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite) * 100)%")
        print(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
        DispatchQueue.main.async {
            self.progress.setProgress(Float(totalBytesWritten) / Float(totalBytesExpectedToWrite), animated: true)
        }
    }
    //3
    public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
        print("fileOffset = \(fileOffset), expectedTotalBytes = \(expectedTotalBytes)")
    }

URLSessionDownloadDelegate有三個(gè)代理方法

  1. 下載完成的回調(diào), 改回調(diào)的location是下載的文件路徑, URLSessionDownloadTask任務(wù)會(huì)把文件下載到tmp文件, tmp文件是一個(gè)臨時(shí)文件里面的數(shù)據(jù)會(huì)被系統(tǒng)刪除, 所以當(dāng)收到下載完成的回調(diào)時(shí), 要立馬把該文件移動(dòng)到其他的文件目錄下.
  2. 下載進(jìn)度的回調(diào), 下載任務(wù)是默認(rèn)異步線(xiàn)程, 刷新UI需要回到UI線(xiàn)程
  3. 斷點(diǎn)下載時(shí)的文件偏移量

需要注意的是, 如果實(shí)現(xiàn)了URLSessionDownloadDelegate, 則URLSessionDataDelegate的方法只有func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)會(huì)在文件下載完成好會(huì)執(zhí)行, 在didFinishDownloadingTo代理執(zhí)行完之后再執(zhí)行該方法

每一個(gè)URLSessionDownloadTask管理者自己的下載任務(wù), 創(chuàng)建完任務(wù)后, 每執(zhí)行一次sessionTask.resume()便開(kāi)始了一個(gè)異步下載任務(wù), 不同的任務(wù)之間相互不影響. 如果要實(shí)現(xiàn)多個(gè)下載任務(wù), 則只需自己處理好數(shù)據(jù)接受即可.

暫停下載, 繼續(xù)下載, 取消下載

1. 暫停下載

如果暫停成功, 則在閉包里返回當(dāng)前已經(jīng)下載的文件, 如果繼續(xù)下載只需把該文件存儲(chǔ)起來(lái), 以供繼續(xù)下載時(shí)使用

open func cancel(byProducingResumeData completionHandler: @escaping (Data?) -> Void)
2. 繼續(xù)下載
let sessionTask = session.downloadTask(withResumeData: data)
sessionTask.resume()

withResumeData參數(shù)是繼續(xù)下載的文件, 傳入該文件, 然后執(zhí)行resume()方法便可以繼續(xù)下載. 繼續(xù)下載時(shí)會(huì)執(zhí)行

public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
        print("fileOffset = \(fileOffset), expectedTotalBytes = \(expectedTotalBytes)")
    }

fileOffset為已經(jīng)下載的數(shù)據(jù)量, expectedTotalBytes為需要下載的數(shù)據(jù)量

這里有一個(gè)坑, 當(dāng)我們使用open func downloadTask(with request: URLRequest) -> URLSessionDownloadTask創(chuàng)建一個(gè)下載任務(wù)時(shí), 返回值是一個(gè)URLSessionDownloadTask, 是一個(gè)新的下載任務(wù)
當(dāng)我們暫停下載, 在已經(jīng)下載的基礎(chǔ)上繼續(xù)下載時(shí)open func downloadTask(withResumeData resumeData: Data) -> URLSessionDownloadTask, 該方法也是創(chuàng)建了一個(gè)新的下載任務(wù), 返回值也是URLSessionDownloadTask, 與之前的未下載完成的那個(gè)下載任務(wù)已經(jīng)沒(méi)有關(guān)系了, 要恢復(fù)下載執(zhí)行resume()方法時(shí), 要使用downloadTask(withResumeData resumeData: Data)返回的下載任務(wù). 否則無(wú)效.

只有滿(mǎn)足以下條件赤嚼,才能恢復(fù)下載:

  1. 自您第一次請(qǐng)求資源以來(lái),資源沒(méi)有變化
  2. 該任務(wù)是HTTP或HTTPS GET請(qǐng)求
  3. 服務(wù)器在其響應(yīng)中提供ETag或Last-Modified標(biāo)題(或兩者)
  4. 服務(wù)器支持字節(jié)范圍請(qǐng)求
  5. 系統(tǒng)尚未刪除臨時(shí)文件(如果磁盤(pán)不足, 系統(tǒng)會(huì)自動(dòng)刪除tmp里的文件)
3. 取消下載
 URLSessionTask.cancel()

該方法是不能恢復(fù)下載的

查看URLSessionTask頭文件, 有一個(gè)方法suspend(), 該方法也是暫停操作, 暫停之后再執(zhí)行resume()也是可以恢復(fù)下載. 但是系統(tǒng)沒(méi)有方法給我們返回已經(jīng)下載的內(nèi)容, 以供以后繼續(xù)下載. 如果App被殺掉, 則無(wú)法繼續(xù)執(zhí)行resume()來(lái)完成下載. 雖然suspend()方法會(huì)把已經(jīng)下載的文件放在tmp文件夾里, 心想可以讀取tmp里面的文件, 然后執(zhí)行downloadTask(withResumeData: data)繼續(xù)下載, 但是該文件的名稱(chēng)是沒(méi)有規(guī)律的, 無(wú)法知道該文件的名稱(chēng), 也就無(wú)法獲取數(shù)據(jù). (ps, 如果有更好的方法可以實(shí)現(xiàn), 煩請(qǐng)告知下)

后臺(tái)下載

let configuration = URLSessionConfiguration.background(withIdentifier: 
  "bgSessionConfiguration")

注意:后臺(tái)配置只能創(chuàng)建一個(gè)會(huì)話(huà)顺又,因?yàn)橄到y(tǒng)使用配置的標(biāo)識(shí)符將任務(wù)與會(huì)話(huà)相關(guān)聯(lián).

使用后臺(tái)配置模式, 當(dāng)我們把應(yīng)用程序退到后臺(tái)時(shí), 應(yīng)用程序?qū)⒗^續(xù)在后臺(tái)下載, 當(dāng)下載完成時(shí), 應(yīng)用程序?qū)?huì)收到handleEventsForBackgroundURLSessionurlSessionDidFinishEvents的通知

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    }

 func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        DispatchQueue.main.async {
        //更細(xì)UI
        }
    }

先執(zhí)行AppDelegate的代理, 再執(zhí)行URLSessionDelegate的代理, 可以在回調(diào)里面對(duì)下載文件做一些處理, 比如更新UI

開(kāi)啟后臺(tái)要在后有一個(gè)疑問(wèn), 開(kāi)啟下載, tmp文件夾里沒(méi)有臨時(shí)文件, 如果不使用后臺(tái)下載就會(huì)有臨時(shí)文件, 不知道這個(gè)是什么原因.

https://cloud.tencent.com/developer/section/1189854

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末更卒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子稚照,更是在濱河造成了極大的恐慌蹂空,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件果录,死亡現(xiàn)場(chǎng)離奇詭異上枕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)弱恒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)辨萍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人斤彼,你說(shuō)我怎么就攤上這事分瘦≌盒海” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵嘲玫,是天一觀的道長(zhǎng)悦施。 經(jīng)常有香客問(wèn)我,道長(zhǎng)去团,這世上最難降的妖魔是什么抡诞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮土陪,結(jié)果婚禮上昼汗,老公的妹妹穿的比我還像新娘。我一直安慰自己鬼雀,他們只是感情好顷窒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著源哩,像睡著了一般鞋吉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上励烦,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天谓着,我揣著相機(jī)與錄音,去河邊找鬼坛掠。 笑死赊锚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屉栓。 我是一名探鬼主播舷蒲,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼友多!你這毒婦竟也來(lái)了阿纤?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夷陋,失蹤者是張志新(化名)和其女友劉穎欠拾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體骗绕,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藐窄,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酬土。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荆忍。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刹枉,到底是詐尸還是另有隱情叽唱,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布微宝,位于F島的核電站棺亭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蟋软。R本人自食惡果不足惜镶摘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岳守。 院中可真熱鬧凄敢,春花似錦、人聲如沸湿痢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)譬重。三九已至俊卤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間害幅,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工岂昭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留以现,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓约啊,卻偏偏與公主長(zhǎng)得像邑遏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恰矩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容