在 swift 中使用 NSURLSession 時,看到了一篇 文章 使用 NSURLSession 從 iTunes 下載歌曲雄右,也包含暫停空骚、繼續(xù)下載、模擬進度不脯、取消下載的功能府怯。但文章中一些技術(shù)細節(jié)稍微老舊了些刻诊,故防楷,在這里重新整理一下,方便日后學習则涯。
完整項目地址 TracksDownload-iTunes
準備工作
- Xcode 版本要求 7.3 及以上复局,我用的Xcode7.3,OS X 版本要求 10.11.0 及以上
- 從 這里 下載基礎(chǔ)工程粟判。解壓縮亿昏,運行程序,會看到一個基本的界面档礁,界面上有個 SearchBar 和空的 TableView角钩,如下圖
NSRULSession 簡介
NSRULSession
在技術(shù)上不僅是一個類,而且也是一套處理基于 HTTP/HTTPS 請求的類呻澜。通過下圖來了解一下它的構(gòu)成
NSRULSession
是收递礼、發(fā) HTTP 請求的關(guān)鍵對象,它可以通過一個配置體 NSURLSessionConfiguration
創(chuàng)建羹幸。這個配置體可以設(shè)置 session 的超時時間脊髓,緩存策略,以及 HTTP headers 栅受,它可以由三種方式創(chuàng)建:
-
defaultSessionConfiguration
:通過這個方法生成的對象将硝,會用默認的方式管理上傳和下載的任務(wù) ,并本地持久化 cache屏镊,cookie 和 信任證書 -
ephemeralSessionConfiguration
:和上面的方法類似,區(qū)別在于它會把會話相關(guān)的數(shù)據(jù)最優(yōu)化的存儲在內(nèi)存中而芥,并從內(nèi)存中取這些數(shù)據(jù) -
backgroundSessionConfiguration
:系統(tǒng)會把上傳或下載任務(wù)放在單獨的進程蔚出,允許這些任務(wù)在后臺進行弟翘,及時這個 app 被后臺掛起或終止,session 的傳輸也不會停止(如果你雙擊home鍵稀余,向上滑動 app 進行關(guān)閉,那么所有的 session 都會中斷)
NSRULSession
的所有的任務(wù)都需要關(guān)聯(lián)一個任務(wù) NSURLSessionTask
對象睛琳,這個對象是任務(wù)的實際執(zhí)行者盒蟆,進行數(shù)據(jù)的獲取师骗,下載或上傳文件辟癌。這個對象有三種類型:
-
NSURLSessionDataTask
:用這種類型的對象做 HTTP GET 請求,從服務(wù)器檢索數(shù)據(jù)寡夹,并存到內(nèi)存中 -
NSURLSessionUploadTask
:用這種類型的對象把磁盤中的文件上傳到服務(wù)器厂置,典型地,通過 HTTP POST 或 PUT 方法 -
NSURLSessionDownloadTask
:用這種類型的對象從服務(wù)器下載文件智绸,并存到一個臨時的文件地址
你可以暫停瞧栗、繼續(xù)和取消一個任務(wù)醉顽。NSURLSessionDownloadTask
支持任務(wù)暫停游添,并在以后繼續(xù)下載
一般地,NSURLSession
通過兩種方式返回數(shù)據(jù):一. 任務(wù)完成或失敗后找都,通過一個 completionHandler
塊返回數(shù)據(jù)廊酣;二. 在創(chuàng)建 session 時,指定一個代理方法晓猛,任務(wù)結(jié)束后通過回調(diào)方法返回數(shù)據(jù)
了解了 NSURLSession
的基本知識后,接下來開始實際操作
查詢歌曲
要查詢歌曲栗恩,需要借助 iTunes Search API 磕秤,在 UISearchBar 中捧韵,輸入關(guān)鍵字再来,然后點擊回車,進行搜索癞己。
首先梭伐,在 SearchViewController.swift 中糊识,在
var searchResults = [TrackModel]()
下面 添加以下代碼:
// 歌曲查詢 session 和 task
let session_queryTracks = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
var task_queryTracks: NSURLSessionTask?
- 第一句摔蓝,我們通過默認的 configuration 生成了一個
NSURLSession
對象 - 第二句贮尉,聲明一個
NSURLSessionTask
類型變量,用它進行 HTTP GET 請求败砂,從 iTunes 的服務(wù)器查詢歌曲魏铅。每次用戶發(fā)起新的查詢時览芳,這個變量都會被重新初始化并循環(huán)使用
然后,需要借助 UISearchBar 的代理方法 searchBarSearchButtonClicked(_:)
铸敏,來捕獲用戶的搜索行為。在 SearchViewController.swift 中找到這個代理方法搔谴,更新為如下代碼:
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
dismissKeyboard()
let searchString = searchBar.text!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if !searchString.isEmpty {
// 1
if task_queryTracks != nil {
task_queryTracks?.cancel()
}
// 2
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
// 3 設(shè)置允許包含在搜索關(guān)鍵詞中的字符
let expectedCharSet = NSCharacterSet.URLQueryAllowedCharacterSet()
let searchTerm = searchString.stringByAddingPercentEncodingWithAllowedCharacters(expectedCharSet)
// 4
let urlString = "http://itunes.apple.com/search?media=music&entity=song&term=\(searchTerm!)"
let url = NSURL(string: urlString)
// 5 生成查詢?nèi)蝿?wù)對象
task_queryTracks = session_queryTracks.dataTaskWithURL(url!, completionHandler: { [unowned self](data, response, error) in
// 6
dispatch_async(dispatch_get_main_queue(), {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
// 7
if let error = error {
print(error.localizedDescription)
}
else if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
self.updateSearchResults(data)
}
}
})
// 8 開始查詢
task_queryTracks?.resume()
}
}
按著上面的注釋標號敦第,依次說明一下:
- //1. 每次用戶查詢時店量,都會檢查 task_queryTracks 是否已經(jīng)初始化融师,如果初始化了,那么就取消上一次搜索任務(wù)舀射,以便開始新的任務(wù)搜索怀伦,并重新利用 task_queryTracks
- //2. 在狀態(tài)欄顯示小菊花房待,告訴用戶,系統(tǒng)正在進行網(wǎng)絡(luò)任務(wù)
- //3. 搜索的關(guān)鍵字被傳入 URL 前拜鹤,把一些不被允許的字符過濾掉
- //4. 根據(jù) iTunes Search API 敏簿,把處理過的內(nèi)容當做 GET 請求的參數(shù)宣虾,生成一個 NSURL 對象
- //5. 初始化一個 NSURLSessionDataTask 對象安岂,來處理 HTTP GET 請求,任務(wù)完成后咙边,數(shù)據(jù)會在 completionHandler 塊中返回
- //6. 在主線程隱藏狀態(tài)欄的菊花,表明網(wǎng)絡(luò)任務(wù)結(jié)束
- //7. 如果成功了王带,則調(diào)用方法
updateSearchResults(_:)
來處理收到的NSData
數(shù)據(jù)愕撰,并更新 TableView - //8. 調(diào)用
resume()
開始搜索任務(wù)
運行 app醋寝,可以搜索任意一首歌,比如輸入 Swift
囱桨,回車搜索舍肠,會出現(xiàn)下圖的效果:
準備下載歌曲
下載歌曲時窘面,為了允許用戶暫停财边、繼續(xù)、取消下載们童,并且能顯示下載進度,我們建立一個下載的 Model 跷跪,來保存下載狀態(tài)吵瞻。在 Model 文件夾下,新建類文件眯停,命名為 DownloadModel 如圖:
在文件 DownloadModel.swift 中莺债,添加以下代碼:
class DownloadModel {
var downloadUrl: String
var isDownloading = false
var downloadProgress = 0.0
var downloadTask: NSURLSessionDownloadTask?
var downloadResumeData: NSData?
init(downloadUrl: String) {
self.downloadUrl = downloadUrl
}
}
簡單介紹一下這些屬性:
-
downloadUrl
:歌曲的下載地址齐邦,唯一標識一個DownloadModel
-
isDownloading
: 歌曲是否正在下載 -
downloadProgress
:歌曲下載進度,0.0~1.0 -
downloadTask
:歌曲下載的一個 Task 對象 -
downloadResumeData
:暫停時我纪,得到的恢復數(shù)據(jù)丐吓,包含繼續(xù)下載的信息(iTunes 服務(wù)器支持斷點下載)
建立下載任務(wù)
有了這個 Model 之后券犁,為了追蹤每一個下載任務(wù),切換到 SearchViewController.swift 文件妓羊,找到
var searchResults = [TrackModel]()
在它下面一行泼舱,添加以下代碼:
var trackDownload = [String: DownloadModel]()
lazy var session_downloadTracks: NSURLSession = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
return session
}()
- 第一句是做了一個下載的映射枷莉,唯一的 url 對應(yīng)一個下載 Model笤妙,來追蹤歌曲的下載狀態(tài)
- 第二句生成下載歌曲的 Session,這個 Session 只用于生成下載歌曲用的
NSURLSessionDownloadTask
股毫。其中铃诬,設(shè)置了代理苍凛,來處理與 Session 相關(guān)的事件,比如可以在代理方法中得到下載的進度宣肚,數(shù)據(jù)等霉涨。我們設(shè)置 delegateQueue 為 nil,默認的俘枫,系統(tǒng)會在一個串行隊列中進行代理方法的調(diào)用以及執(zhí)行的結(jié)果方法調(diào)用 - 使用
lazy
關(guān)鍵字鸠蚪,系統(tǒng)不立刻生成session_downloadTracks
這個對象师溅,而是我們使用它時墓臭,系統(tǒng)才去創(chuàng)建
接下來,來實現(xiàn) NSURLSession 的代理方法酌摇。在文件 SearchViewController.swift 的最底部嗡载,加入以下代碼:
extension SearchTracksViewController: NSURLSessionDownloadDelegate {
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
print("下載結(jié)束")
}
}
- NSURLSessionDownloadDelegate 定義了使用 NSURLSession 下載某些任務(wù)時洼滚,用到的代理方法遥巴。一個下載任務(wù)結(jié)束的時候,方法
URLSession(_:downloadTask:didFinishDownloadingToURL:)
都會被調(diào)用拾弃。
我們來觸發(fā)下載任務(wù)砸彬。當用戶點擊 Download 按鈕時斯入,會調(diào)用方法 startDownload(_:)
刻两,在此方法中執(zhí)行下載任務(wù)滴某,找到這個方法,更新為以下代碼:
func startDownload(track: TrackModel) {
if let urlString = track.trackPreviewUrl, url = NSURL(string:urlString) {
let download = DownloadModel(downloadUrl: urlString)
download.downloadTask = session_downloadTracks.downloadTaskWithURL(url)
download.downloadTask?.resume()
download.isDownloading = true
trackDownload[urlString] = download
}
}
- 當用戶點擊下載的時候饼灿,在此方法中生成一個
DownloadModel
對象帝美,保存了下載中的歌曲狀態(tài)悼潭,并映射到 字典trackDownload
舰褪。
運行這個 app 占拍,搜索任意一首歌晃酒,點擊下載,過一會就會收到一條打印信息:"下載結(jié)束"
初茶。表示下載結(jié)束浊闪。
保存并播放歌曲
歌曲下載完之后搁宾,會調(diào)用方法 URLSession(_:downloadTask:didFinishDownloadingToURL:)
盖腿。方法里有個參數(shù) URL,是文件的臨時存放地址鸟款,我們要做到的就是把這個文件拷貝一個指定的地址(本地持久化)何什。然后等龙,我們需要把已經(jīng)完成的任務(wù)從字典 trackDownload
中移除,并更新相應(yīng)的 tableViewCell黍衙。
為了方便找到對應(yīng)的 cell荠诬,我們在 SearchViewController.swift 文件中添加一個輔助方法浅妆,用來返回 cell 所在的索引 index。代碼如下:
func cellIndexOfDownloadTrack(downloadTrack:NSURLSessionDownloadTask) -> Int? {
if let url = downloadTrack.originalRequest?.URL?.absoluteString {
for (index, track) in searchResults.enumerate() {
if url == track.trackPreviewUrl {
return index
}
}
}
return nil
}
下一步就要開始把文件拷貝到我們指定的地址辩尊。更新代理方法 URLSession(_:downloadTask:didFinishDownloadingToURL:)
如下:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) {
// 1
let originalURL: String? = downloadTask.originalRequest?.URL?.absoluteString
if let url = originalURL, destinationURL = localFilePathForUrl(url) {
print(destinationURL)
// 2
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.removeItemAtURL(destinationURL)
} catch {
//
}
do {
try fileManager.copyItemAtURL(location, toURL: destinationURL)
} catch let error as NSError {
print("Could not copy file to disk:\(error.localizedDescription)")
}
}
// 3
if let url = originalURL {
trackDownload[url] = nil
// 4
if let index = cellIndexOfDownloadTrack(downloadTask) {
dispatch_async(dispatch_get_main_queue(), {
[unowned self] in
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index,inSection: 0)], withRowAnimation: .None)
})
}
}
}
對于上面代碼標注的關(guān)鍵步驟,做一個簡單說明:
- //1. 我們提取出下載任務(wù)的原始 URL胸墙,然后找到 app 的 Documents 路徑按咒,在這個路徑后拼接原始 URL的lastPathComponent励七,得到一個新的路徑,就是我們需要的目標路徑
- //2. 把文件從臨時路徑 location 拷貝到目標路徑之前吼野,先使用 NSFileManager 清理目標路徑下的數(shù)據(jù)瞳步,然后再執(zhí)行拷貝
- //3. 從數(shù)據(jù)結(jié)構(gòu)中刪除這個不再需要的 downloadTask 對象
- //4. 根據(jù)索引腰奋,更新相應(yīng)的 tableviewCell
運行 app氛堕,搜索一首歌,點擊下載括儒,稍等片刻就會收到一條打印信息:
下載按鈕也會消失帮寻,點擊已經(jīng)下載的歌曲固逗,就會彈出
MPMoviePlayerViewController
進行播放藕帜,如圖:
模擬下載進度
模擬下載進度時洽故,我們需要知道兩點:
- 已接收的數(shù)據(jù)量
- 總數(shù)據(jù)量
協(xié)議 NSURLSessionDownloadDelegate
的代理方法中时甚,有一個方法帶有我們需要的這兩個參數(shù),在文件 SearchViewController.swift 中梨熙,找到對這個協(xié)議的擴展咽扇,添加下面的方法:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 1
if let url = downloadTask.originalRequest?.URL?.absoluteString, trackDownload = trackDownload[url] {
// 2
trackDownload.downloadProgress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
// 3
let totalSize = NSByteCountFormatter.stringFromByteCount(totalBytesExpectedToWrite, countStyle: .Binary)
// 4
if let index = cellIndexOfDownloadTrack(downloadTask), trackCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TrackCell {
dispatch_async(dispatch_get_main_queue(), {
trackCell.v_progress.progress = trackDownload.downloadProgress
trackCell.lb_progress.text = String(format: "%.1f%% of %@", trackDownload.downloadProgress*100,totalSize)
})
}
}
}
接下來一步步分析代碼中的標注:
- //1. 使用參數(shù) downloadTask质欲,提取其中的 URL把敞,然后根據(jù) URL 找到對應(yīng)的下載 Model
- //2. 這一步是關(guān)鍵榨惠,參數(shù) totalBytesWritten 代表已經(jīng)接收并寫入臨時文件的數(shù)據(jù)赠橙,參數(shù) totalBytesExpectedToWrite 代表總數(shù)據(jù)量,兩個值求商就是當前的下載比例掉奄。然后保存到下載 Model 的 downloadProgress 屬性中
- //3. NSByteCountFormatter 可以把數(shù)據(jù)量轉(zhuǎn)換為人們易懂的字節(jié)數(shù)姓建,比如轉(zhuǎn)換后變?yōu)?50 KB
- //4. 最后找到這首歌曲對應(yīng)的 cell速兔,然后更新 cell 上的進度等
為了在 cell 上正確的顯示下載狀態(tài),找到方法 tableView(_:cellForRowAtIndexPath:)
谍婉,在
let track = searchResults[indexPath.row]
下面添加代碼:
var showDownloadControls = false
if let download = trackDownload[track.trackPreviewUrl!] {
showDownloadControls = true
cell.v_progress.progress = download.downloadProgress
cell.lb_progress.text = download.isDownloading ? "Downloading..." : "Paused"
}
cell.v_progress.hidden = !showDownloadControls
cell.lb_progress.hidden = !showDownloadControls
對于將要下載的歌曲穗熬,顯示 “Downloading...”丁溅,暫停的顯示 “Paused”唧瘾,并且根據(jù)下載狀態(tài)隱藏or顯示 v_progress 和 lb_progress饰序。對于正在下載的歌曲,下載按鈕也要隱藏塌衰,所以最疆,這句代碼
cell.btn_download.hidden = trackHaveDownloaded
更新為
cell.btn_download.hidden = trackHaveDownloaded || showDownloadControls
運行 app蚤告,下載一首歌杜恰,看一下下載效果,如圖所示:
暫停、繼續(xù)亡嫌、取消下載
......
暫停
......
暫停時,會產(chǎn)生恢復數(shù)據(jù) resume data
于购,根據(jù)這里面的數(shù)據(jù)圃郊,可以在以后繼續(xù)下載持舆,前提是服務(wù)器支持斷點下載逸寓。
并且不是所有的條件下都可以繼續(xù)下載的覆山,具體哪些情況可以繼續(xù)下載簇宽,請參考 文檔
找到方法 pauseDownload(_:)
,更新為以下代碼:
func pauseDownload(track: TrackModel) {
if let url = track.trackPreviewUrl, download = trackDownload[url] {
if download.isDownloading {
download.downloadTask?.cancelByProducingResumeData({ (data) in
if data != nil {
download.downloadResumeData = data
}
})
download.isDownloading = false
}
}
}
上面的代碼中譬嚣,通過調(diào)用方法 cancelByProducingResumeData(_:)
钞它,得到了 resume data
遭垛,然后把這個 data 保存到相應(yīng)的下載 Model 中锯仪,方便以后繼續(xù)下載。并更新 Model 中的屬性 isDownloading
小腊,表示停止下載溢豆。
......
繼續(xù)
......
找到方法 resumeDownload(_:)
瘸羡,更新為以下代碼:
func resumeDownload(track: TrackModel) {
if let previewUrl = track.trackPreviewUrl, download = trackDownload[previewUrl] {
if let resumeData = download.downloadResumeData {
download.downloadTask = session_downloadTracks.downloadTaskWithResumeData(resumeData)
download.downloadTask!.resume()
download.isDownloading = true
}
else if let url = NSURL(string: download.downloadUrl) {
download.downloadTask = session_downloadTracks.downloadTaskWithURL(url)
download.downloadTask!.resume()
download.isDownloading = true
}
}
}
在這個方法中,我們判斷如果有 resume data
卷仑,那么調(diào)用方法 downloadTaskWithResumeData(_:)
來繼續(xù)下載锡凝。如果沒有垢啼,就重新下載 芭析。兩種情況下馁启,都更新下載狀態(tài)為 true。
......
取消
......
取消下載就比較簡單了翠勉,找到方法 cancelDownload(_:)
对碌,更新為以下代碼:
func cancelDownload(track: TrackModel) {
if let url = track.trackPreviewUrl, download = trackDownload[url] {
download.downloadTask?.cancel()
trackDownload[url] = nil
}
}
在這個方法中俭缓,找到需要取消的下載任務(wù)酥郭,然后調(diào)用方法 cancel()
就會取消下載不从,并從字典中刪掉這個任務(wù)椿息。
最后要做的就是更新 cell 的工作了√跆颍回到方法 tableView(_:cellForRowAtIndexPath:)
孟抗,在 if
塊中,添加下面的代碼:
let title = download.isDownloading ? "Pause" : "Resume"
cell.btn_pause.setTitle(title, forState: .Normal)
在
cell.lb_progress.hidden = !showDownloadControls
下面添加以下代碼:
cell.btn_pause.hidden = !showDownloadControls
cell.btn_cancel.hidden = !showDownloadControls
整個工作到此結(jié)束铅协,運行 app狐史,下載幾首歌骏全,并進行暫停婉刀,恢復突颊,取消律秃,效果如下圖所示:
總結(jié)
在建立 DownloadModel 的時候棒动,里面的 DownloadModel 最好是個 class
類型宾添,而不要聲明為 struct
類型,正如本項目中建立的一樣缕陕。因為 struct
類型是 value type
粱锐,class
類型是 reference type
它們之間的區(qū)別請查看 Swift: 概念解釋
本項目中,會對 DownloadModel 的對象所持有的屬性扛邑,比如 isDownloading
等進行多次的修改怜浅。如果 DownloadModel 是 struct
類型,那么每次修改過之后蔬崩,都需要再更新一遍字典 trackDownload
中對應(yīng)的 model恶座,因為 struct
類型的對象在傳遞的過程中沥阳,是重新拷貝一份的跨琳,拷貝后得到的數(shù)據(jù)并不指向原始地址。而 class
類型是 引用類型桐罕,故在傳遞過程中脉让,這個對象都是指向原始地址的樟氢,對它的修改,也會影響原始數(shù)據(jù)侠鳄。
我們可以對比一下 DownloadModl 為 class
類型和 struct
類型兩種情況下埠啃,代碼的差異性:
- DownloadModl 為
class
類型:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 1
if let url = downloadTask.originalRequest?.URL?.absoluteString, trackDownload = trackDownload[url] {
// 2
trackDownload.downloadProgress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
// 3
let totalSize = NSByteCountFormatter.stringFromByteCount(totalBytesExpectedToWrite, countStyle: .Binary)
// 4
if let index = cellIndexOfDownloadTrack(downloadTask), trackCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TrackCell {
dispatch_async(dispatch_get_main_queue(), {
trackCell.v_progress.progress = trackDownload.downloadProgress
trackCell.lb_progress.text = String(format: "%.1f%% of %@", trackDownload.downloadProgress*100,totalSize)
})
}
}
}
- DownloadModl 為
struct
類型:
func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// 1
if let url = downloadTask.originalRequest?.URL?.absoluteString {
download = trackDownload[url]! as DownloadModl
// 2
download.downloadProgress = Float(totalBytesWritten)/Float(totalBytesExpectedToWrite)
// 3
let totalSize = NSByteCountFormatter.stringFromByteCount(totalBytesExpectedToWrite, countStyle: .Binary)
// 4
if let index = cellIndexOfDownloadTrack(downloadTask), trackCell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: index, inSection: 0)) as? TrackCell {
dispatch_async(dispatch_get_main_queue(), {
trackCell.v_progress.progress = download.downloadProgress
trackCell.lb_progress.text = String(format: "%.1f%% of %@", download.downloadProgress*100,totalSize)
})
}
trackDownload[url] = download
}
}
注意區(qū)分上面兩種情況下,使用 struct
類型會方便很多伟恶,不然碴开,類似的還有方法 pauseDownload(_:)
、resumeDownload(_:)
等博秫,都需要做相應(yīng)調(diào)整潦牛。