HWIFileDownload
HWIFileDownload 通过 NSURLSession
在 iOS 上简化文件下载。可以单独控制并行文件下载的所有可能操作:开始、取消、暂停、恢复。下载进度可以通过 NSProgress
原生报告每个单独的文件以及总进度。
特点
基于 NSURLSession
的 HWIFileDownload 即使应用程序未运行,也提供系统后台操作。可以单独启动下载、取消、暂停和恢复。支持所有可能的状态:未开始、等待下载、已开始(下载中)、完成、暂停、取消、中断、错误。在恢复取消的下载时,会重用之前下载的数据。《NSProgress》用于进度报告和取消/暂停/恢复事件传播。
HWIFileDownload 与 iOS 6 向后兼容(在那里使用 NSURLConnection
而不是 NSURLSession
)。
安装
您可以通过手动操作或将 HWIFileDownload 添加到项目中。
手动安装
HWIFileDownload由以下文件组成
- HWIBackgroundSessionCompletionHandlerBlock.h
- HWIFileDownloadDelegate.h
- HWIFileDownloader.h
- HWIFileDownloader.m
- HWIFileDownloadItem.h
- HWIFileDownloadItem.m
- HWIFileDownloadProgress.h
- HWIFileDownloadProgress.m
所有文件都需要添加到您的应用程序项目中。
使用 CocoaPods 安装
要将 HWIFileDownload 集成到 Xcode 项目中,使用 CocoaPods,请将以下命令添加到您的 Podfile
中
pod 'HWIFileDownload'
然后运行
$ pod install
使用 HWIFileDownload
集成后使用 HWIFileDownload,需要在您希望使用 HWIFileDownload 的 Objective-C 类文件中导入头文件 HWIFileDownloader.h
#import "HWIFileDownloader.h"
对于 Swift 使用,您需要将导入添加到您的 Bridging-Header 文件中
#import "HWIBackgroundSessionCompletionHandlerBlock.h"
#import "HWIFileDownloadDelegate.h"
#import "HWIFileDownloader.h"
#import "HWIFileDownloadItem.h"
#import "HWIFileDownloadProgress.h"
实现
HWIFileDownload使用一个下载标识符来启动下载、检索进度信息以及处理下载完成情况。该下载标识符是一个字符串,对于每个独立的文件下载必须唯一。
要启动下载,应用程序客户端调用 HWIFileDownloader
的 startDownloadWithIdentifier:fromRemoteURL:
方法。
将下载存储作为代理
应用程序客户端必须维护一个自定义的下载存储来管理下载和持久化存储。应用程序的下载存储需要实现协议 HWIFileDownloadDelegate
以在关键的下载事件上被调用。
代理在下载完成时被调用。其他必要的调用控制着网络活动指示器的可见性。可选地,代理可以在每个下载项的下载进度变更时被调用。为了控制下载文件的本地名称和位置,代理可以实现方法 localFileURLForIdentifier:remoteURL:
。
Objective-C
@protocol HWIFileDownloadDelegate
- (void)downloadDidCompleteWithIdentifier:(nonnull NSString *)identifier
localFileURL:(nonnull NSURL *)localFileURL;
- (void)downloadFailedWithIdentifier:(nonnull NSString *)identifier
error:(nonnull NSError *)error
httpStatusCode:(NSInteger)httpStatusCode
errorMessagesStack:(nullable NSArray<NSString *> *)errorMessagesStack
resumeData:(nullable NSData *)resumeData;
- (void)incrementNetworkActivityIndicatorActivityCount;
- (void)decrementNetworkActivityIndicatorActivityCount;
@optional
- (void)downloadProgressChangedForIdentifier:(nonnull NSString *)identifier;
- (void)downloadPausedWithIdentifier:(nonnull NSString *)identifier
resumeData:(nullable NSData *)resumeData;
- (void)resumeDownloadWithIdentifier:(nonnull NSString *)identifier;
- (nullable NSURL *)localFileURLForIdentifier:(nonnull NSString *)identifier
remoteURL:(nonnull NSURL *)remoteURL;
- (BOOL)downloadAtLocalFileURL:(nonnull NSURL *)localFileURL isValidForDownloadIdentifier:(nonnull NSString *)downloadIdentifier;
- (BOOL)httpStatusCode:(NSInteger)httpStatusCode isValidForDownloadIdentifier:(nonnull NSString *)downloadIdentifier;
- (void)customizeBackgroundSessionConfiguration:(nonnull NSURLSessionConfiguration *)backgroundSessionConfiguration;
- (nullable NSURLRequest *)urlRequestForRemoteURL:(nonnull NSURL *)remoteURL;
- (void)onAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge
downloadIdentifier:(nonnull NSString *)downloadIdentifier
completionHandler:(void (^ _Nonnull)(NSURLCredential * _Nullable credential, NSURLSessionAuthChallengeDisposition disposition))completionHandler;
- (nullable NSProgress *)rootProgress;
@end
Swift 示例类实现协议
class DownloadStore: NSObject, HWIFileDownloadDelegate {
// HWIFileDownloadDelegate (mandatory)
@objc public func downloadDidComplete(withIdentifier identifier: String, localFileURL: URL) {
print("yes")
}
@objc public func downloadFailed(withIdentifier identifier: String, error: Error, httpStatusCode: Int, errorMessagesStack: [String]?, resumeData: Data?) {
print("no")
}
@objc public func incrementNetworkActivityIndicatorActivityCount() {
//
}
@objc public func decrementNetworkActivityIndicatorActivityCount() {
//
}
// HWIFileDownloadDelegate (optional)
/*
@objc public func downloadProgressChanged(forIdentifier identifier: String) {
//
}
@objc public func downloadPaused(withIdentifier identifier: String, resumeData: Data?) {
//
}
@objc public func resumeDownload(withIdentifier identifier: String) {
//
}
@objc public func localFileURL(forIdentifier identifier: String, remoteURL: URL) -> URL? {
return nil
}
@objc public func download(atLocalFileURL localFileURL: URL, isValidForDownloadIdentifier downloadIdentifier: String) -> Bool {
return true
}
@objc public func httpStatusCode(_ httpStatusCode: Int, isValidForDownloadIdentifier downloadIdentifier: String) -> Bool {
return true
}
@objc public func customizeBackgroundSessionConfiguration(_ backgroundSessionConfiguration: URLSessionConfiguration) {
//
}
@objc public func urlRequest(forRemoteURL remoteURL: URL) -> URLRequest? {
return nil
}
@objc public func onAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, downloadIdentifier: String, completionHandler: @escaping (URLCredential?, URLSession.AuthChallengeDisposition) -> Void) {
//
}
@objc public func rootProgress() -> Progress? {
return nil
}
*/
下载器
应用程序需要保留一个 HWIFileDownloader
实例,该实例用于管理下载过程。HWIFileDownloader
提供启动、查询和控制单独的下载过程的方法。
- (nonnull instancetype)initWithDelegate:(nonnull NSObject<HWIFileDownloadDelegate>*)delegate;
- (void)startDownloadWithIdentifier:(nonnull NSString *)identifier
fromRemoteURL:(nonnull NSURL *)remoteURL;
- (void)startDownloadWithIdentifier:(nonnull NSString *)identifier
usingResumeData:(nonnull NSData *)resumeData;
- (BOOL)isDownloadingIdentifier:(nonnull NSString *)identifier;
- (BOOL)isWaitingForDownloadOfIdentifier:(nonnull NSString *)identifier;
- (BOOL)hasActiveDownloads;
- (void)cancelDownloadWithIdentifier:(nonnull NSString *)identifier;
- (nullable HWIFileDownloadProgress *)downloadProgressForIdentifier:(nonnull NSString *)identifier;
进度
HWIFileDownloadProgress
公开了以下属性
@property (nonatomic, assign, readonly) float downloadProgress;
@property (nonatomic, assign, readonly) int64_t expectedFileSize;
@property (nonatomic, assign, readonly) int64_t receivedFileSize;
@property (nonatomic, assign, readonly) NSTimeInterval estimatedRemainingTime;
@property (nonatomic, assign, readonly) NSUInteger bytesPerSecondSpeed;
@property (nonatomic, strong, readwrite, nullable) NSString *lastLocalizedDescription;
@property (nonatomic, strong, readwrite, nullable) NSString *lastLocalizedAdditionalDescription;
@property (nonatomic, strong, readonly, nonnull) NSProgress *nativeProgress;
示例应用程序
示例应用程序展示了如何将 HWIFileDownload 集成到Objective-C应用程序中。
应用程序的 下载存储 是通过自定义类 DemoDownloadStore
实现的。
示例应用程序的代理持有 DemoDownloadStore
和 HWIFileDownloader
的实例。
工作流程和场景
启动和重新启动
应用程序启动时,收集所有下载的列表。
暂停和继续
在“暂停”时,下载将被停止。不完整下载的数据将作为恢复数据保留。使用“恢复”可以继续下载,从已下载的数据开始。
在iOS 6上,暂停和恢复不可用。在iOS 7和iOS 8上,恢复数据需要由应用程序客户端管理。自iOS 9以来,使用恢复方法,NSProgress
以透明的方式管理恢复数据。
取消
点击“取消”时,下载将停止。不会保留任何恢复数据。不会提供重新下载功能。
崩溃
点击“崩溃”时,应用会崩溃。在iOS 7(及以后版本)中,即使应用不再运行,启动的下载也会在后台继续。在iOS 6中,下载不会继续。
强制退出
应用被用户终止后,后台下载不会继续。在iOS 7(及以后版本)中,应用再次启动后会传递回恢复数据。中断的下载可以继续。
后台
在后台运行时,在iOS 7(及以后版本)中,所有正在进行的下载将继续。在iOS 6中,所有正在进行的下载作为后台任务持续约10分钟。
网络中断
在网络连接丢失后,所有正在进行的下载在请求超时后将暂停。在iOS 7(及以后版本)中,当网络再次可用时,下载将恢复。在iOS 6中,在网络请求超时后下载会停止;在下次应用启动时重新开始。
定制化
两个委托调用提供了调整连接参数的钩子。
- (void)customizeBackgroundSessionConfiguration:(nonnull NSURLSessionConfiguration *)backgroundSessionConfiguration;
- (nullable NSURLRequest *)urlRequestForRemoteURL:(nonnull NSURL *)remoteURL; // iOS 6 only
超时
通过代理调用,可以自定义超时行为。在iOS中,有两种超时时间:请求超时和资源超时。
请求超时(在数据传输过程中如果没有数据传输超过给定超时值,则触发,并且当数据传输时被重置)。iOS系统默认值是60秒。
资源超时(在(layer)NSURLSession中可用)指的是资源在给定超时时间内无法检索到的情况。即使数据正在接收,资源超时也会触发。当没有下载任务的背景会话中第一次下载任务恢复时,它会重置。iOS系统默认值是604800秒(7天)。
如果网络请求的主机不可达,NSURLConnection会在请求开始后立即检查主机可用性,如果主机不可达则立即失败并返回错误(NSURLErrorDomain Code=-1003 “无法找到指定主机名的主机”)。NSURLSession只在资源超时触发时才会终止。
身份验证
如果文件下载需要身份验证,您需要实现代理方法
- (void)onAuthenticationChallenge:(nonnull NSURLAuthenticationChallenge *)challenge
downloadIdentifier:(nonnull NSString *)downloadIdentifier
completionHandler:(void (^ _Nonnull)(NSURLCredential * _Nullable credential, NSURLSessionAuthChallengeDisposition disposition))completionHandler;
示例应用代码包括一个停用的示例实现。
集成
应用程序代理
请查阅示例代码,了解如何将源代码与应用程序代理集成。
依赖项
HWIFileDownload没有第三方依赖项。
Font Awesome
示例应用使用Font Awesome来下载、取消、暂停、继续、完成、错误和取消图标。
注释
请注意,iOS 10的一个系统错误导致在恢复下载后,直到iOS 10.2,无法正确报告进度。 随着 iOS 10.2 的发布,Apple修复了这个错误(见https://github.com/Heikowi/HWIFileDownload/issues/23)。