設計一個健壯的大型檔案下載系統

BEASTQ發表於2017-04-14

img

我想要讓 NSScreencast iOS 應用支援下載視訊供離線使用。每個視訊大小為 80-200 MB,這就需要打造一個健壯的下載系統。

第一步是在章節頁面新增顯示進度的下載按鈕。類似於你在 iTunes 下載歌曲。點選下載按鈕動畫進入圓形下載進度指示器。該下載在後臺進行,並向訂閱的按鈕傳送進度通知,以在 UI 中顯示百分比進度。

實際的下載是通過 NSOperation 子類完成的。這種方式提供了很大的便利,例如控制併發性、服務優先順序,並且擁有便捷的機制取消執行的中的下載。下載進度通過通知傳送到外界,通知包含章節 ID,所有感興趣的 UI 可以監聽該通知並做出更新。

當然,我們不需要在下載時讓使用者盯著螢幕,因此使用者可以自由切換應用甚至暫停它,下載都可以繼續。

接下來,我需要在統一的位置顯示掛起和之前的下載,用於方便檢視哪些內容正在下載,哪些下載完成,並可以離線觀看和刪除視訊。

該頁面在表檢視中以行顯示每個章節的資料。各行顯示的下載內容需要在收到下載進度變化通知時快速重新整理。

為了實現上述功能,我把該狀態儲存成 Core Data 模型。我的 DownloadInfo 模型如下所示:

有了 Core Data,我們可以跟蹤下載的整個生命週期狀態。以前,我用過 plists 儲存,但(我敢說) Core Data 比 plists 更容易進行簡單儲存。

這裡,我把下載進度百分比儲存到模型中,但我不是反覆儲存該值。在快速連線中,你會得到一大堆進度變化(每秒數十次),且沒必要每次都儲存到 Core Data 中。我只在取消請求並希望在 UI 中看到下載的百分比時才儲存資料。

儲存資料到 Core Data 的另外一個好處是我們可以利用 NSFetchedResultsController 快速構建 DownloadsViewController

錯誤處理

所有涉及網路的操作都有機會發生錯誤。大型檔案的下載更是加劇這個機會。人們走出 Wi-Fi 範圍,進入隧道,搭乘飛機以及其他大量事情都會干擾下載。為了確保最佳的使用者體驗,我要把它處理成允許使用者快速重試(並在某些情況下,可以自動重試)。

當發生失敗,把 DownloadInfostate 標記為 .failed,UI 隨之更新。單元格過載,使用者可以點選重試該下載。

暫停和恢復

NSURLSession API 引入時,它就可以取消請求併產生一個不透明的物件,叫做 resume data。使用這種技術,你可以從它停下來的地方開始請求,唯一需要考慮的問題只是如何把該資料持久化,以備恢復下載。這樣把該資料新增到我的 DownloadInfo 模型就很合適了。當使用者點選正在進行的下載,會呼叫 downloadTask.cancel(byProducing:) 並儲存 resume data 以供日後使用。

當下載開始,如果該模型存在 resume data,就會從上一次停止的位置繼續下載。該功能很容易新增,但對大檔案下載真的是超實用的。

其中要特別小心,參照 resume data is currently broken on iOS 10

處理蜂窩網路

我不希望任何資料的損壞,所以,預設設定 NSURLSessionConfigurationallowsCellularAccess 設定為否。然後設定一個標識來切換該值狀態,因為有些人可能希望無限制使用它。

使用 FX Reachability 監聽網路連線狀態。如果使用者希望在蜂窩網路下下載章節,我們彈出切換設定,讓下載繼續。

保持模型和檔案同步

由於我們把檔案的後設資料儲存在磁碟,所以需要確保這些後設資料總是同步的。當你刪除一個章節,必須同時刪除磁碟上 Core Data 中的模型。為確保同步,我用 CleanupDownloadsOperation 檢查磁碟上章節有對應的一個 DownloadInfo (除非資料被刪除),並且每個下載檔案都記錄在 Core Data 中(除非被刪除)。

在這裡,如果萬一有發生錯誤,兩種狀態(資料庫/磁碟)必須取消同步。

後臺下載

雖然這看似簡單,後臺下載會帶來大量的複雜混亂的情況,下一篇會講到這個主題。

他們說,只是加個離線下載嘛

當我最初想為 app 在釋出前新增離線下載功能,但發現這需要額外一到兩天的工作,這其中是相當的複雜。

我想,這就是軟體開發。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

設計一個健壯的大型檔案下載系統 設計一個健壯的大型檔案下載系統

相關文章