詳細解析幾個和網(wǎng)絡請求有關的類(二十七) —— NSURLSession(三)

版本記錄

版本號 時間
V1.0 2019.06.15 星期六

前言

我們做APP發(fā)起網(wǎng)絡請求,一般都是使用框架瓶摆,這些框架的底層也都是蘋果的API,接下來幾篇就一起來看一下和網(wǎng)絡有關的幾個類性宏。感興趣的可以看上面幾篇文章群井。
1. 詳細解析幾個和網(wǎng)絡請求有關的類 (一) —— NSURLSession
2. 詳細解析幾個和網(wǎng)絡請求有關的類(二) —— NSURLRequest和NSMutableURLRequest
3. 詳細解析幾個和網(wǎng)絡請求有關的類(三) —— NSURLConnection
4. 詳細解析幾個和網(wǎng)絡請求有關的類(四) —— NSURLSession和NSURLConnection的區(qū)別
5. 詳細解析幾個和網(wǎng)絡請求有關的類(五) —— 關于NSURL加載系統(tǒng)(一)
6. 詳細解析幾個和網(wǎng)絡請求有關的類(六) —— 使用NSURLSession(二)
7. 詳細解析幾個和網(wǎng)絡請求有關的類(七) —— URL數(shù)據(jù)的編碼和解碼(三)
8. 詳細解析幾個和網(wǎng)絡請求有關的類(八) —— 處理重定向和其他請求更改(四)
9. 詳細解析幾個和網(wǎng)絡請求有關的類(九) —— 身份驗證挑戰(zhàn)和TLS鏈驗證(五)
10. 詳細解析幾個和網(wǎng)絡請求有關的類(十) —— 理解獲取緩存(六)
11. 詳細解析幾個和網(wǎng)絡請求有關的類(十一) —— Cookies和自定義協(xié)議(七)
12. 詳細解析幾個和網(wǎng)絡請求有關的類(十二) —— URL Session的生命周期(八)
13. 詳細解析幾個和網(wǎng)絡請求有關的類(十三) —— NSURLResponse(一)
14. 詳細解析幾個和網(wǎng)絡請求有關的類(十四) —— NSHTTPCookie(一)
15. 詳細解析幾個和網(wǎng)絡請求有關的類(十五) —— NSHTTPCookieStorage(一)
16. 詳細解析幾個和網(wǎng)絡請求有關的類(十六) —— NSURLCache(一)
17. 詳細解析幾個和網(wǎng)絡請求有關的類(十七) —— NSCachedURLResponse(一)
18. 詳細解析幾個和網(wǎng)絡請求有關的類(十八) —— NSURLAuthenticationChallenge(一)
19. 詳細解析幾個和網(wǎng)絡請求有關的類(十九) —— NSURLProtectionSpace(一)
20. 詳細解析幾個和網(wǎng)絡請求有關的類(二十) —— NSURLCredential(一)
21. 詳細解析幾個和網(wǎng)絡請求有關的類(二十一) —— NSURLCredentialStorage(一)
22. 詳細解析幾個和網(wǎng)絡請求有關的類(二十二) —— NSStream(一)
23. 詳細解析幾個和網(wǎng)絡請求有關的類(二十三) —— NSInputStream(一)
24. 詳細解析幾個和網(wǎng)絡請求有關的類(二十四) —— NSOutputStream(一)
25. 詳細解析幾個和網(wǎng)絡請求有關的類(二十五) —— NSHTTPCookie之設置刪除和通信(二)
26. 詳細解析幾個和網(wǎng)絡請求有關的類(二十六) —— NSURLError錯誤碼及其相關作用(一)

開始

首先看下寫作環(huán)境

Swift 5, iOS 12, Xcode 10

在此URLSession教程中,您將學習如何創(chuàng)建HTTP請求以及實現(xiàn)可以暫停和恢復的后臺下載毫胜。

無論應用程序從服務器檢索應用程序數(shù)據(jù)书斜,更新您的社交媒體狀態(tài)還是將遠程文件下載到磁盤诬辈,網(wǎng)絡請求都是讓奇跡發(fā)生的原因。 為了幫助您滿足網(wǎng)絡請求的許多要求荐吉,Apple提供了URLSession焙糟,這是一個用于上傳和下載內容的完整網(wǎng)絡API。

在本教程中样屠,您將學習如何構建Half Tunes酬荞,一個查詢iTunes Search API的應用程序,然后下載30秒的歌曲預覽瞧哟。 完成的應用程序將支持后臺傳輸混巧,并允許用戶暫停,恢復或取消正在進行的下載勤揩。

打開下載好的入門項目咧党,它包含用于搜索歌曲和顯示搜索結果的用戶界面,具有一些存根功能的網(wǎng)絡類以及用于存儲和播放曲目的輔助方法陨亡。 這使您可以專注于實現(xiàn)應用程序的網(wǎng)絡方面傍衡。

構建并運行項目。 您將在頂部看到一個帶有搜索欄的視圖负蠕,下面是一個空表視圖:

在搜索欄中鍵入查詢蛙埂,然后點按Search。 視圖仍然是空的遮糖。 不過不用擔心绣的,您將使用新的URLSession調用更改此情況。


URLSession Overview

在開始之前欲账,了解URLSession及其組成類非常重要屡江,因此請查看下面的快速概述。

URLSession既是一類赛不,也是一套用于處理基于HTTPHTTPS的請求的類:

URLSession是負責發(fā)送和接收請求的關鍵對象惩嘉。 您可以通過URLSessionConfiguration創(chuàng)建它,它有三種形式:

  • default:創(chuàng)建使用磁盤持久全局緩存踢故,憑據(jù)和cookie存儲對象的默認配置對象文黎。
  • ephemeral:與default配置類似,不同之處在于您將所有與會話相關的數(shù)據(jù)存儲在內存中殿较。 將此視為“私人”會話耸峭。
  • background:允許會話在后臺執(zhí)行上載或下載任務。 即使應用程序本身被系統(tǒng)暫托敝或終止抓艳,傳輸仍會繼續(xù)触机。

URLSessionConfiguration還允許您配置會話屬性帚戳,例如超時timeout值玷或,緩存策略和HTTP標頭。 有關配置選項的完整列表片任,請參Apple’s documentation偏友。

URLSessionTask是一個表示任務對象的抽象類。 會話創(chuàng)建一個或多個任務來執(zhí)行獲取數(shù)據(jù)和下載或上載文件的實際工作对供。

1. Understanding Session Task Types

有三種類型的具體會話任務:

  • URLSessionDataTask:將此任務用于GET請求位他,以將數(shù)據(jù)從服務器檢索到內存。
  • URLSessionUploadTask:使用此任務通過POSTPUT方法將文件從磁盤上載到Web服務。
  • URLSessionDownloadTask:使用此任務將文件從遠程服務下載到臨時文件位置。

您還可以暫停毙死,恢復和取消任務舞蔽。 URLSessionDownloadTask具有暫停以供將來恢復的額外功能。

通常伦籍,URLSession以兩種方式返回數(shù)據(jù):

  • 當任務完成時,成功或出錯都會走completion handler
  • 通過調用在創(chuàng)建會話時設置的代理上的方法醒串。

既然您已經(jīng)了解了URLSession可以做什么,那么您已準備好將理論付諸實踐鄙皇!


DataTask and DownloadTask

首先芜赌,您將創(chuàng)建一個數(shù)據(jù)任務,以便在iTunes Search API中查詢用戶的搜索詞伴逸。

SearchViewController.swift中缠沈,searchBarSearchButtonClicked啟用狀態(tài)欄上的網(wǎng)絡活動指示器,以向用戶顯示網(wǎng)絡進程正在運行错蝴。 然后它調用getSearchResults(searchTerm:completion :)博烂,它在QueryService.swift中被刪除。 您即將構建它以發(fā)出網(wǎng)絡請求漱竖。

QueryService.swift中禽篱,使用以下內容替換// TODO 1

let defaultSession = URLSession(configuration: .default)

// TODO 2用下面替換

var dataTask: URLSessionDataTask?

這就是你所做的:

  • 1) 創(chuàng)建了一個URLSession并使用default會話配置對其進行了初始化。
  • 2) 聲明了URLSessionDataTask馍惹,用于在用戶執(zhí)行搜索時向iTunes搜索Web服務發(fā)出GET請求躺率。 每次用戶輸入新的搜索字符串時,都會重新初始化數(shù)據(jù)任務万矾。

接下來悼吱,使用以下內容替換getSearchResults(searchTerm:completion :)中的內容:

// 1
dataTask?.cancel()
    
// 2
if var urlComponents = URLComponents(string: "https://itunes.apple.com/search") {
  urlComponents.query = "media=music&entity=song&term=\(searchTerm)"      
  // 3
  guard let url = urlComponents.url else {
    return
  }
  // 4
  dataTask = 
    defaultSession.dataTask(with: url) { [weak self] data, response, error in 
    defer {
      self?.dataTask = nil
    }
    // 5
    if let error = error {
      self?.errorMessage += "DataTask error: " + 
                              error.localizedDescription + "\n"
    } else if 
      let data = data,
      let response = response as? HTTPURLResponse,
      response.statusCode == 200 {       
      self?.updateSearchResults(data)
      // 6
      DispatchQueue.main.async {
        completion(self?.tracks, self?.errorMessage ?? "")
      }
    }
  }
  // 7
  dataTask?.resume()
}

依次記錄每個編號的評論:

  • 1) 對于新用戶查詢,您將取消已存在的任何數(shù)據(jù)任務良狈,因為您要為此新查詢重用數(shù)據(jù)任務對象后添。
  • 2) 要在查詢URL中包含用戶的搜索字符串,請從iTunes Search base URL創(chuàng)建URLComponents薪丁,然后設置其查詢字符串遇西。這可確保您的搜索字符串使用轉義字符馅精。
  • 3) urlComponentsurl屬性是可選的,因此您將其解包為url并在它為nil時提前return粱檀。
  • 4) 在您創(chuàng)建的會話中洲敢,使用查詢URL初始化URLSessionDataTask,并在數(shù)據(jù)任務完成時調用完成處理程序茄蚯。
  • 5) 如果請求成功压彭,則調用輔助方法updateSearchResults,該方法將響應data解析為tracks數(shù)組渗常。
  • 6) 切換到主隊列以將tracks傳遞給completion handler壮不。
  • 7) 默認情況下,所有任務都以掛起狀態(tài)啟動皱碘。調用resume()啟動數(shù)據(jù)任務忆畅。

SearchViewController中,查看對getSearchResults(searchTerm:completion :)的調用中的完成閉包尸执。隱藏活動指示器后家凯,它會將results存儲在searchResults中,然后更新表視圖如失。

注意:默認請求方法是GET绊诲。如果要將數(shù)據(jù)任務設置為POST,PUT或DELETE褪贵,請使用url創(chuàng)建URLRequest掂之,設置請求的HTTPMethod屬性,然后使用URLRequest而不是URL創(chuàng)建數(shù)據(jù)任務脆丁。

構建并運行您的應用程序世舰。搜索任何歌曲,您將看到表格視圖填充相關的曲目結果槽卫,如下所示:

有了一些URLSession代碼跟压,Half Tunes現(xiàn)在有點功能了!

能夠查看歌曲結果很不錯歼培,但如果你可以點擊一首歌下載它會不會更好震蒋? 這是你的下一個業(yè)務訂單。 您將使用download task躲庄,這樣可以輕松地將歌曲片段保存在本地文件中查剖。

1. Downloading Classes

處理多次下載需要做的第一件事是創(chuàng)建一個自定義對象來保存活動下載的狀態(tài)。

Model組中創(chuàng)建一個名為Download.swift的新Swift文件噪窘。

打開Download.swift笋庄,并在Foundation導入下面添加以下實現(xiàn):

class Download {
  var isDownloading = false
  var progress: Float = 0
  var resumeData: Data?
  var task: URLSessionDownloadTask?
  var track: Track
  
  init(track: Track) {
    self.track = track
  }
}

這是Download屬性的簡要說明:

  • isDownloading:下載是正在進行還是暫停。
  • progress:下載的小數(shù)進度,表示為介于0.0和1.0之間的浮點數(shù)直砂。
  • resumeData:存儲用戶暫停下載任務時生成的Data菌仁。 如果主機服務器支持它,您的應用程序可以使用它來恢復暫停的下載哆键。
  • task:下載trackURLSessionDownloadTask掘托。
  • track:要下載的曲目瘦锹。 trackurl屬性也充當Download的唯一標識符籍嘹。

接下來,在DownloadService.swift中弯院,將// TODO 4替換為以下屬性:

var activeDownloads: [URL: Download] = [:]

該字典將維護URL與其活動Download之間的映射(如果有)辱士。


URLSession Delegates

您可以使用完成處理程序completion handler創(chuàng)建下載任務,就像創(chuàng)建數(shù)據(jù)任務data task時一樣听绳。 但是颂碘,在本教程的后面部分,您將檢查并更新下載進度椅挣,這需要您實現(xiàn)自定義委托头岔。 所以你現(xiàn)在也可以這樣做。

Apple’s URLSession documentation中列出了幾種會話代理協(xié)議鼠证。 URLSessionDownloadDelegate處理特定于下載任務的任務級事件峡竣。

您將需要盡快將SearchViewController設置為會話委托,因此現(xiàn)在您將創(chuàng)建一個符合會話委托協(xié)議的擴展量九。

打開SearchViewController.swift并將// TODO 5替換為以下URLSessionDownloadDelegate擴展名:

extension SearchViewController: URLSessionDownloadDelegate {
  func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                  didFinishDownloadingTo location: URL) {
    print("Finished downloading to \(location).")
  } 
}

唯一的非可選URLSessionDownloadDelegate方法是urlSession(_:downloadTask:didFinishDownloadingTo :)适掰,應用程序在下載完成后會調用該方法。 目前荠列,只要下載完成类浪,您就會打印一條消息。

1. Downloading a Track

通過所有準備工作肌似,您現(xiàn)在可以將文件下載到位费就。 您的第一步是創(chuàng)建一個專用會話來處理您的下載任務。

SearchViewController.swift中川队,使用以下代碼替換// TODO 6

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

在這里受楼,您使用默認配置初始化單獨的會話,并指定一個委托呼寸,該委托允許您通過委托調用接收URLSession事件艳汽。 這對于監(jiān)視任務的進度非常有用。

將委托隊列delegate queue設置為nil會導致會話創(chuàng)建一個串行操作隊列对雪,以執(zhí)行委派方法和完成處理程序的所有調用河狐。

請注意downloadsSession的延遲創(chuàng)建;這使您可以在初始化視圖控制器之后延遲創(chuàng)建會話。 這樣做允許您將self作為委托參數(shù)傳遞給會話初始化程序馋艺。

現(xiàn)在使用以下行替換viewDidLoad()末尾的// TODO 7

downloadService.downloadsSession = downloadsSession

這會將DownloadServicedownloadsSession屬性設置為您剛剛定義的會話栅干。

通過配置會話和代理,您最終可以在用戶請求跟蹤下載時創(chuàng)建下載任務捐祠。

DownloadService.swift中碱鳞,使用以下實現(xiàn)替換startDownload(_ :)的內容:

// 1
let download = Download(track: track)
// 2
download.task = downloadsSession.downloadTask(with: track.previewURL)
// 3
download.task?.resume()
// 4
download.isDownloading = true
// 5
activeDownloads[download.track.previewURL] = download

當用戶點擊表格視圖單元格的Download按鈕時,作為TrackCellDelegateSearchViewController會識別此單元格的Track踱蛀,然后使用該Track調用startDownload(_ :)窿给。

這是startDownload(_ :)中發(fā)生的事情:

  • 1) 首先使用軌道track初始化Download
  • 2) 使用新的會話對象率拒,使用track’s preview URL創(chuàng)建URLSessionDownloadTask崩泡,并將其設置為Downloadtask屬性。
  • 3) 您可以通過調用resume()來啟動下載任務猬膨。
  • 4) 您表明下載正在進行中角撞。
  • 5) 最后,將下載URL映射到activeDownloads中的Download勃痴。

構建并運行您的應用程序谒所,搜索任何track,然后點擊單元格上的Download按鈕沛申。 過了一會兒劣领,您將在調試控制臺中看到一條消息,表示下載已完成污它。

Finished downloading to file:///Users/mymac/Library/Developer/CoreSimulator/Devices/74A1CE9B-7C49-46CA-9390-3B8198594088/data/Containers/Data/Application/FF0D263D-4F1D-4305-B98B-85B6F0ECFE16/tmp/CFNetworkDownload_BsbzIk.tmp.

Download按鈕仍在顯示剖踊,但您很快就會解決這個問題。 首先衫贬,你想要播放一些曲調德澈!

2. Saving and Playing the Track

下載任務完成后,urlSession(_:downloadTask:didFinishDownloadingTo :)會提供臨時文件位置的URL固惯,如打印消息中所示梆造。 您的工作是在從方法返回之前將其移動到應用程序的沙箱容器目錄中的永久位置。

SearchViewController.swift中葬毫,使用以下代碼替換urlSession(_:downloadTask:didFinishDownloadingTo :)中的print語句:

// 1
guard let sourceURL = downloadTask.originalRequest?.url else {
  return
}

let download = downloadService.activeDownloads[sourceURL]
downloadService.activeDownloads[sourceURL] = nil
// 2
let destinationURL = localFilePath(for: sourceURL)
print(destinationURL)
// 3
let fileManager = FileManager.default
try? fileManager.removeItem(at: destinationURL)

do {
  try fileManager.copyItem(at: location, to: destinationURL)
  download?.track.downloaded = true
} catch let error {
  print("Could not copy file to disk: \(error.localizedDescription)")
}
// 4
if let index = download?.track.index {
  DispatchQueue.main.async { [weak self] in
    self?.tableView.reloadRows(at: [IndexPath(row: index, section: 0)], 
                               with: .none)
  }
}

以下是您在每個步驟中所做的事情:

  • 1) 您從任務中提取原始請求URL镇辉,在活動下載(active downloads)中查找相應的下載并從該字典中刪除它。
  • 2) 然后贴捡,將URL傳遞給localFilePath(for :)忽肛,它通過將URLlastPathComponent(文件的文件名和擴展名)附加到應用程序的Documents目錄的路徑,生成要保存的永久本地文件路徑烂斋。
  • 3) 使用fileManager屹逛,將下載的文件從其臨時文件位置移動到所需的目標文件路徑础废,首先清除該位置的任何項目,然后再開始復制任務罕模。 您還將下載track’sdownloaded屬性設置為true评腺。
  • 4) 最后,使用下載track’sindex屬性重新加載相應的單元格淑掌。

構建并運行項目蒿讥,運行查詢,然后選擇任何track并下載它抛腕。 下載完成后芋绸,您將看到打印到控制臺的文件路徑位置:

file:///Users/mymac/Library/Developer/CoreSimulator/Devices/74A1CE9B-7C49-46CA-9390-3B8198594088/data/Containers/Data/Application/087C38CC-0CEB-4895-ADB6-F44D13C2CA5A/Documents/mzaf_2494277700123015788.plus.aac.p.m4a

Download按鈕現(xiàn)在消失,因為委托方法將trackdownloaded屬性設置為true兽埃。 點按track侥钳,您將聽到它在AVPlayerViewController中播放适袜,如下所示:


Pausing, Resuming, and Canceling Downloads

如果用戶想暫停下載或完全取消該怎么辦柄错? 在本節(jié)中,您將實現(xiàn)暫停苦酱,恢復和取消功能售貌,以便用戶完全控制下載過程。

您將首先允許用戶取消活動下載active download疫萤。

1. Canceling Downloads

DownloadService.swift中颂跨,在cancelDownload(_ :)中添加以下代碼:

guard let download = activeDownloads[track.previewURL] else {
  return
}

download.task?.cancel()
activeDownloads[track.previewURL] = nil

要取消下載,您將從active downloads詞典中的相應Download中檢索下載任務扯饶,并在其上調用cancel()以取消該任務恒削。 然后,您將從active downloads字典中刪除下載對象尾序。

2. Pausing Downloads

您的下一個任務是讓您的用戶暫停下載并稍后再繼續(xù)钓丰。

暫停下載與取消下載類似。 暫停取消下載任務每币,但也會生成恢復數(shù)據(jù)( resume data)携丁,其中包含足夠的信息,以便在主機服務器支持該功能時稍后恢復下載兰怠。

注意:您只能在特定條件下恢復下載梦鉴。 例如,自您第一次請求資源以來揭保,資源不得更改肥橙。 有關完整的條件列表,請查看here的文檔秸侣。

使用以下代碼替換pauseDownload(_ :)的內容:

guard
  let download = activeDownloads[track.previewURL],
  download.isDownloading 
  else {
    return
}

download.task?.cancel(byProducingResumeData: { data in
  download.resumeData = data
})

download.isDownloading = false

這里的關鍵區(qū)別是你調用cancel(byProducingResumeData :)而不是cancel()存筏。 您為此方法提供了一個閉包參數(shù)娜庇,該參數(shù)允許您將恢復數(shù)據(jù)resume data保存到相應的Download以供將來恢復。

您還將DownloadisDownloading屬性設置為false方篮,以指示用戶已暫停下載名秀。

現(xiàn)在暫停功能已經(jīng)完成,下一個業(yè)務順序是允許用戶恢復暫停的下載藕溅。

3. Resuming Downloads

使用以下代碼替換resumeDownload(_ :)的內容:

guard let download = activeDownloads[track.previewURL] else {
  return
}

if let resumeData = download.resumeData {
  download.task = downloadsSession.downloadTask(withResumeData: resumeData)
} else {
  download.task = downloadsSession
    .downloadTask(with: download.track.previewURL)
}

download.task?.resume()
download.isDownloading = true

當用戶恢復下載時匕得,請檢查相應的Download是否存在恢復數(shù)據(jù)resume data。 如果找到巾表,您將通過使用恢復數(shù)據(jù)調用downloadTask(withResumeData :)來創(chuàng)建新的下載任務汁掠。 如果由于任何原因缺少恢復數(shù)據(jù),您將使用下載URL創(chuàng)建新的下載任務集币。

在任何一種情況下考阱,您都將通過調用resume啟動任務,并將DownloadisDownloading標志設置為true以指示下載已恢復鞠苟。


Showing and Hiding the Pause/Resume and Cancel Buttons

這三個功能只需要執(zhí)行一項操作:您需要根據(jù)需要顯示或隱藏Pause/Resume and Cancel按鈕乞榨。

要做到這一點,TrackCellconfigure(track:downloaded:)需要知道該track是否有active download以及它是否正在下載当娱。

TrackCell.swift中吃既,將configure(track:downloaded :)更改為configure(track:downloaded:download :)

func configure(track: Track, downloaded: Bool, download: Download?) {

SearchViewController.swift中,調用tableView(_:cellForRowAt:)

cell.configure(track: track,
               downloaded: track.downloaded,
               download: downloadService.activeDownloads[track.previewURL])

在這里跨细,您從activeDownloads中提取track的下載對象鹦倚。

回到TrackCell.swift,在configure(track:downloaded:download :)中找到// TODO 14并添加以下屬性:

var showDownloadControls = false

然后使用以下內容替換// TODO 15

if let download = download {
  showDownloadControls = true
  let title = download.isDownloading ? "Pause" : "Resume"
  pauseButton.setTitle(title, for: .normal)
}

正如注釋所述冀惭,非nil下載對象意味著下載正在進行中震叙,因此單元格應顯示下載控件:Pause/Resume and Cancel。 由于暫停和恢復功能共享相同的按鈕散休,您將根據(jù)需要在兩種狀態(tài)之間切換按鈕媒楼。

在這個if-closure下面,添加以下代碼:

pauseButton.isHidden = !showDownloadControls
cancelButton.isHidden = !showDownloadControls

在此處溃槐,僅在下載處于活動狀態(tài)時才顯示單元格的按鈕匣砖。

最后,替換此方法的最后一行:

downloadButton.isHidden = downloaded

使用以下代碼:

downloadButton.isHidden = downloaded || showDownloadControls

在這里昏滴,如果正在下載曲目track猴鲫,則告訴單元格隱藏Download按鈕。

構建并運行您的項目谣殊。 同時下載幾首曲目拂共,您將能夠隨意暫停,恢復和取消它們:


Showing Download Progress

此時姻几,應用程序正常運行宜狐,但未顯示下載進度势告。 要改善用戶體驗,您需要更改應用以偵聽下載進度事件并在單元格中顯示進度抚恒。 有一個會話委托方法咱台,非常適合這項工作!

首先俭驮,在TrackCell.swift中回溺,使用以下輔助方法替換// TODO 16

func updateDisplay(progress: Float, totalSize : String) {
  progressView.progress = progress
  progressLabel.text = String(format: "%.1f%% of %@", progress * 100, totalSize)
}

track cell具有progressViewprogressLabel outlets。 委托方法將調用此幫助程序方法來設置其值混萝。

接下來遗遵,在SearchViewController.swift中,將以下委托方法添加到URLSessionDownloadDelegate擴展:

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
                  didWriteData bytesWritten: Int64, totalBytesWritten: Int64,
                  totalBytesExpectedToWrite: Int64) {
  // 1
  guard
    let url = downloadTask.originalRequest?.url,
    let download = downloadService.activeDownloads[url]  
    else {
      return
  }
  // 2
  download.progress = 
    Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
  // 3
  let totalSize = 
    ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, 
                              countStyle: .file) 
  // 4
  DispatchQueue.main.async {
    if let trackCell = 
      self.tableView.cellForRow(at: IndexPath(row: download.track.index,
                                              section: 0)) as? TrackCell {
      trackCell.updateDisplay(progress: download.progress, 
                              totalSize: totalSize)
    }
  }
}

仔細研究這種委托方法逸嘀,一步一步:

  • 1) 您提取所提供的downloadTaskURL车要,并使用它在您的活動下載字典中查找匹配的Download
  • 2) 該方法還提供了您編寫的總字節(jié)數(shù)以及您希望寫入的總字節(jié)數(shù)崭倘。您將進度計算為這兩個值的比率翼岁,并將結果保存在Download中。track單元格將使用此值更新進度視圖绳姨。
  • 3) ByteCountFormatter獲取一個字節(jié)值并生成一個人類可讀的字符串登澜,顯示總下載文件大小阔挠。您將使用此字符串顯示下載大小以及完成百分比飘庄。
  • 4) 最后,您找到負責顯示Track的單元格购撼,并調用單元格的輔助方法以使用從前面步驟派生的值更新其progress view and progress label跪削。這涉及到UI,因此您可以在主隊列中執(zhí)行此操作迂求。

Displaying the Download’s Progress

現(xiàn)在碾盐,更新單元的配置以在下載進行時顯示進度視圖和狀態(tài)。

打開TrackCell.swift揩局。在configure(track:downloaded:download :)中毫玖,在設置了pause按鈕標題后,在if-closure中添加以下行:

progressLabel.text = download.isDownloading ? "Downloading..." : "Paused"

這使得單元格在委托方法第一次更新之前顯示凌盯,并且下載暫停時顯示付枫。

現(xiàn)在,在if-closure下面添加以下代碼驰怎,在兩個按鈕的isHidden行下面:

progressView.isHidden = !showDownloadControls
progressLabel.isHidden = !showDownloadControls

這僅在下載過程中顯示progress view and label阐滩。

構建并運行您的項目。 下載任何track县忌,隨著下載的進行掂榔,您應該會看到進度條狀態(tài)更新:


Enabling Background Transfers

此時您的應用程序非常實用继效,但還有一個主要的增強功能:后臺傳輸(Background transfers)

在此模式下装获,即使您的應用在后臺或因任何原因崩潰瑞信,下載也會繼續(xù)。這對于非常小的歌曲片段來說并不是必需的穴豫,但如果您的應用傳輸大型文件喧伞,您的用戶將會喜歡此功能。

但是绩郎,如果您的應用沒有運行潘鲫,這怎么做到的?

操作系統(tǒng)OS在應用程序外部運行一個單獨的守護程序來管理后臺傳輸任務肋杖,并在下載任務運行時將適當?shù)奈邢l(fā)送到應用程序溉仑。如果應用程序在主動傳輸期間終止,則任務將在后臺繼續(xù)運行状植,不受影響浊竟。

任務完成后,守護程序(daemon)將在后臺重新啟動應用程序津畸。重新啟動的應用程序將重新創(chuàng)建后臺會話以接收相關的完成委托消息并執(zhí)行任何所需的操作振定,例如將下載的文件保存到磁盤。

注意:如果用戶通過從應用切換器強制退出來終止應用肉拓,系統(tǒng)將取消所有會話的后臺傳輸后频,并且不會嘗試重新啟動應用。

您可以通過使用后臺background會話配置創(chuàng)建會話來訪問此魔法暖途。

SearchViewController.swift中卑惜,在downloadsSession的初始化中,找到以下代碼行:

let configuration = URLSessionConfiguration.default

替換為下面代碼

let configuration = 
  URLSessionConfiguration.background(withIdentifier:
                                       "com.xxxx.HalfTunes.bgSession")

您將使用特殊的后臺background會話配置驻售,而不是使用默認default會話配置露久。 請注意,您還為會話設置了唯一標識符欺栗,以便您的應用在需要時創(chuàng)建新的后臺會話毫痕。

注意:您不能為后臺配置創(chuàng)建多個會話,因為系統(tǒng)使用配置的標識符將任務與會話相關聯(lián)迟几。


Relaunching Your App

如果后臺任務在應用程序未運行時完成消请,則應用程序將在后臺重新啟動。 您需要從應用代理處理此事件瘤旨。

切換到AppDelegate.swift梯啤,用以下代碼替換// TODO 17

var backgroundSessionCompletionHandler: (() -> Void)?

// TODO 18替換為下面

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

在這里,您將提供的completionHandler保存為app delegate中的變量供以后使用存哲。

application(_:handleEventsForBackgroundURLSession :)喚醒應用程序來處理已完成的后臺任務因宇。您需要在此方法中處理兩個項目:

  • 首先七婴,應用程序需要使用此委托方法提供的標識符重新創(chuàng)建適當?shù)暮笈_配置和會話。但是由于這個應用程序在實例化SearchViewController時會創(chuàng)建后臺會話察滑,所以此時您已經(jīng)重新連接了打厘!
  • 其次,您需要捕獲此委托方法提供的完成處理程序贺辰。調用完成處理程序告訴操作系統(tǒng)您的應用程序已完成當前會話的所有后臺活動户盯。它還會使操作系統(tǒng)對更新的UI進行快照,以便在應用切換器中顯示饲化。

調用提供的完成處理completion handler程序的地方是urlSessionDidFinishEvents(forBackgroundURLSession :)莽鸭,這是一個URLSessionDelegate方法,當后臺會話上的所有任務都完成時觸發(fā)吃靠。

SearchViewController.swift中硫眨,使用以下擴展名替換// TODO 19

extension SearchViewController: URLSessionDelegate {
  func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    DispatchQueue.main.async {
      if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
        let completionHandler = appDelegate.backgroundSessionCompletionHandler {
        appDelegate.backgroundSessionCompletionHandler = nil
        
        completionHandler()
      }
    }
  } 
}

上面的代碼從app delegate中獲取存儲的完成處理程序completion handler,并在主線程上調用它巢块。 您可以通過獲取UIApplication的共享實例找到app delegate礁阁,這可以通過UIKit import進行獲取。


Testing Your App’s Functionality

構建并運行您的應用程序族奢。 開始幾個并發(fā)下載姥闭,然后點擊Home按鈕將應用程序發(fā)送到后臺。 等到您認為下載已完成越走,然后雙擊Home按鈕以顯示應用程序切換器棚品。

下載應該已經(jīng)完成,您應該在應用程序快照中看到它們的新狀態(tài)弥姻。 打開應用程序以確認:

你現(xiàn)在有一個功能音樂流媒體應用程序南片!

如果您想進一步探索該主題,那么URLSession主題將多于本教程中的主題庭敦。 例如,您還可以嘗試上載任務和會話配置設置薪缆,例如超時值和緩存策略秧廉。

要了解有關這些功能(以及其他功能!)的更多信息拣帽,請查看以下資源:

后記

本篇主要講述了一種和網(wǎng)絡請求有關的類NSURLSession减拭,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蔽豺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拧粪,更是在濱河造成了極大的恐慌修陡,老刑警劉巖沧侥,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異魄鸦,居然都是意外死亡宴杀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門拾因,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旺罢,“玉大人,你說我怎么就攤上這事绢记”獯铮” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵蠢熄,是天一觀的道長罩驻。 經(jīng)常有香客問我,道長护赊,這世上最難降的妖魔是什么惠遏? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮骏啰,結果婚禮上节吮,老公的妹妹穿的比我還像新娘。我一直安慰自己判耕,他們只是感情好透绩,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著壁熄,像睡著了一般帚豪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上草丧,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天狸臣,我揣著相機與錄音,去河邊找鬼昌执。 笑死烛亦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的懂拾。 我是一名探鬼主播煤禽,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岖赋!你這毒婦竟也來了檬果?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎选脊,沒想到半個月后杭抠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡知牌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年祈争,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片角寸。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡菩混,死狀恐怖,靈堂內的尸體忽然破棺而出扁藕,到底是詐尸還是另有隱情沮峡,我是刑警寧澤广凸,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布壹蔓,位于F島的核電站,受9級特大地震影響陷舅,放射性物質發(fā)生泄漏望薄。R本人自食惡果不足惜疟游,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痕支。 院中可真熱鬧颁虐,春花似錦、人聲如沸卧须。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽花嘶。三九已至笋籽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椭员,已是汗流浹背车海。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拆撼,地道東北人容劳。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像闸度,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚜印,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內容