设计一个健壮的大型文件下载系统

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 在发布前添加离线下载功能,但发现这需要额外一到两天的工作,这其中是相当的复杂。

我想,这就是软件开发。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

1 4 收藏 1 评论

关于作者:BEASTQ

iOS 摇滚小怪兽 个人主页 · 我的文章 · 10 ·   

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部