Swift:用NSURLSession下載iTunes歌曲

在 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)整潦牛。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挡育,隨后出現(xiàn)的幾起案子巴碗,更是在濱河造成了極大的恐慌,老刑警劉巖即寒,帶你破解...
    沈念sama閱讀 212,294評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橡淆,死亡現(xiàn)場離奇詭異,居然都是意外死亡母赵,警方通過查閱死者的電腦和手機逸爵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凹嘲,“玉大人师倔,你說我怎么就攤上這事≈懿洌” “怎么了趋艘?”我有些...
    開封第一講書人閱讀 157,790評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凶朗。 經(jīng)常有香客問我瓷胧,道長,這世上最難降的妖魔是什么俱尼? 我笑而不...
    開封第一講書人閱讀 56,595評論 1 284
  • 正文 為了忘掉前任抖单,我火速辦了婚禮,結(jié)果婚禮上遇八,老公的妹妹穿的比我還像新娘矛绘。我一直安慰自己,他們只是感情好刃永,可當我...
    茶點故事閱讀 65,718評論 6 386
  • 文/花漫 我一把揭開白布货矮。 她就那樣靜靜地躺著,像睡著了一般斯够。 火紅的嫁衣襯著肌膚如雪囚玫。 梳的紋絲不亂的頭發(fā)上喧锦,一...
    開封第一講書人閱讀 49,906評論 1 290
  • 那天,我揣著相機與錄音抓督,去河邊找鬼燃少。 笑死,一個胖子當著我的面吹牛铃在,可吹牛的內(nèi)容都是我干的阵具。 我是一名探鬼主播,決...
    沈念sama閱讀 39,053評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼定铜,長吁一口氣:“原來是場噩夢啊……” “哼阳液!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揣炕,我...
    開封第一講書人閱讀 37,797評論 0 268
  • 序言:老撾萬榮一對情侶失蹤帘皿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后畸陡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹰溜,經(jīng)...
    沈念sama閱讀 44,250評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,570評論 2 327
  • 正文 我和宋清朗相戀三年罩锐,在試婚紗的時候發(fā)現(xiàn)自己被綠了奉狈。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,711評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡涩惑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桑驱,到底是詐尸還是另有隱情竭恬,我是刑警寧澤,帶...
    沈念sama閱讀 34,388評論 4 332
  • 正文 年R本政府宣布熬的,位于F島的核電站痊硕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏押框。R本人自食惡果不足惜岔绸,卻給世界環(huán)境...
    茶點故事閱讀 40,018評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望橡伞。 院中可真熱鬧盒揉,春花似錦、人聲如沸兑徘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,796評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挂脑。三九已至藕漱,卻和暖如春欲侮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肋联。 一陣腳步聲響...
    開封第一講書人閱讀 32,023評論 1 266
  • 我被黑心中介騙來泰國打工威蕉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人橄仍。 一個月前我還...
    沈念sama閱讀 46,461評論 2 360
  • 正文 我出身青樓韧涨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沙兰。 傳聞我的和親對象是個殘疾皇子氓奈,可洞房花燭夜當晚...
    茶點故事閱讀 43,595評論 2 350

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