Skip to content
Danie1s edited this page Mar 18, 2020 · 6 revisions

基本用法

初始化

SessionManager是 Tiercel 里最重要的类型,负责开启下载任务、管理任务、操作任务。使用 Tiercel 首要就是创建SessionManager实例

因为需要支持原生后台下载,所以需要在AppDelegate.swift文件里初始化,参考以下做法

// 在 AppDelegate.swift 文件里

// 最好成为AppDelegate的属性
// 注意:不能使用懒加载
var sessionManager: SessionManager = {
    var configuration = SessionConfiguration()
    configuration.allowsCellularAccess = true
    let manager = SessionManager("default", configuration: configuration)
    return manager
}()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // 如果需要做一些配置,那么必须保证在这个方法结束前完成SessionManager初始化
    
    return true
}

// 必须实现此方法,并且把identifier对应的completionHandler保存起来
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {

    if sessionManager.identifier == identifier {
        sessionManager.completionHandler = completionHandler
    }
}

开启下载

初始化完毕后,可以拿到 SessionManager实例直接使用

一行代码开启下载

// 创建下载任务并且开启下载,同时返回可选类型的DownloadTask实例,如果url无效,则返回nil
let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg")

// 批量创建下载任务并且开启下载,返回有效url对应的任务数组,url需要跟fileNames一一对应
let tasks = sessionManager.multiDownload(URLStrings)

开启下载的方法主要接受一个遵守URLConvertible协议的类型参数,在 Tiercel 中默认让 StringURLURLComponents遵守这个协议,也就是可以传入这三种类型的参数来开启下载

public protocol URLConvertible {

    func asURL() throws -> URL
}

状态回调

任务的状态回调

Tiercel 提供了精细的任务状态回调

let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg")

// 回调闭包的参数是DownloadTask实例,可以得到所有相关的信息
// 所有闭包都可以选择是否在主线程上执行,由参数onMainQueue控制,如果onMainQueue传false,则会在sessionManager初始化时指定的队列上执行,onMainQueue默认为true
// progress 闭包:如果任务正在下载,就会触发
// success 闭包:任务已经下载过,或者下载成功,都会触发,这时候task.status == .succeeded
// failure 闭包:只要task.status != .succeeded,就会触发:
//    1. 暂停任务,这时候task.status == .suspended
//    2. 任务下载失败,这时候task.status == .failed
//    3. 取消任务,这时候task.status == .canceled
//    4. 移除任务,这时候task.status == .removed
task?.progress(onMainQueue: true) { (task) in
    let progress = task.progress.fractionCompleted
    print("下载中, 进度:\(progress)")
}.success { (task) in
    print("下载完成")
}.failure { (task) in
    print("下载失败")
}

// 为了让代码更加统一,新增了completion闭包,与success和failure互斥,当使用completion时,success和failure无效
task?.progress(onMainQueue: true) { (task) in
    let progress = task.progress.fractionCompleted
    print("下载中, 进度:\(progress)")
}.completion { (task) in  
    if task.status == .succeeded {
    	// 下载成功
    } else {
    	// 其他状态
    }
}

SessionManager的状态回调

SessionManager作为所有任务的管理者,也可以设置状态回调

sessionManager.progress { [weak self] (manager) in
	//  下载中
}.completion { [weak self] (manager) in
    if manager.status == .succeeded {
        // 下载成功
    } else {
        // 其他状态
    }
}

通知

为了方便使用,Tiercel 还提供了状态通知

public extension DownloadTask {
    static let runningNotification: Notification.Name
    static let didCompleteNotification: Notification.Name
}

public extension SessionManager {
    static let runningNotification: Notification.Name
    static let didCompleteNotification: Notification.Name
}

extension Notification {
    public var downloadTask: DownloadTask? 
    public var sessionManager: SessionManager? 
}

使用方式如下:

// 接受task的通知
NotificationCenter.default.addObserver(forName: DownloadTask.didCompleteNotification, object: nil, queue: nil) { (notification) in
    guard let task = notification.downloadTask else { return }
    print(task.status)
}

操作任务

SessionManager是任务的管理者,任何操作都需要通过SessionManager进行

单个任务

在 Tiercel 中,URL 是下载任务的唯一标识,如果需要对任务进行操作,则传入对应的 URL

let URLString = "http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg"

// 开始下载
// 如果调用suspend暂停了下载,可以调用这个方法继续下载
sessionManager.start(URLString)

// 暂停下载
sessionManager.suspend(URLString)

// 取消下载,没有下载成功的任务会被移除,不保留缓存,已经下载完成的不受影响
sessionManager.cancel(URLString)

// 移除下载,任何状态的任务都会被移除,没有下载完成的缓存文件会被删除,可以选择是否保留已经下载完成的文件
sessionManager.remove(URLString, completely: false)

也可以直接对任务进行操作

let URLString = "http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg"
let task = sessionManager.fetchTask(URLString)

sessionManager.start(task)
sessionManager.suspend(task)
sessionManager.cancel(task)
sessionManager.remove(task, completely: false)

批量操作

除了可以对单个任务进行操作,sessionManager也提供了批量操作的 API

Tiercel 对批量操作的 API 进行了优化,性能会比简单地逐个操作(如:for循环、forEach)高出很多倍,如果需要对多个任务同时进行操作,请使用这些 API

sessionManager.multiDownload(URLStrings)
sessionManager.totalStart()
sessionManager.totalSuspend()
sessionManager.totalCancel()
sessionManager.totalRemove(completely: false)

操作回调

暂停下载、取消下载、移除下载的操作可以添加回调,并且可以选择是否在主线程上执行该闭包

注意:这些回调都只会执行一次

// 暂停下载
sessionManager.cancel(task, onMainQueue: true) { [weak self] (task) in
    self?.tableView.reloadData()
}

// 批量操作的回调
sessionManager.totalCancel() { [weak self] _ in
    self?.tableView.reloadData()
}

后台下载

从 Tiercel 2.0 开始支持原生的后台下载,开发者只需按照本文开头的初始化进行操作,就可以完成所需的配置

只要使用 Tiercel 开启了下载任务:

  • 手动Kill App,任务会暂停,重启App后可以恢复进度,继续下载
  • 只要不是手动Kill App,任务都会一直在下载,例如:
    • App退回后台
    • App崩溃或者被系统关闭
    • 重启手机

如果想了解后台下载的细节和注意事项,可以查看:iOS 原生级别后台下载详解

文件校验

Tiercel 提供了文件校验功能,可以根据需要添加,校验结果在回调的task.validation

let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg")
// 回调闭包可以选择是否在主线程上执行
task?.validateFile(code: "9e2a3650530b563da297c9246acaad5c",
                   type: .md5,
                   onMainQueue: true)
                   { (task) in
    if task.validation == .correct {
        // 文件正确
    } else {
        // 文件错误
    }
}

FileChecksumHelper是文件校验的工具类,可以直接使用它对已经存在的文件进行校验

/// 对文件进行校验,是在子线程进行的
///
/// - Parameters:
///   - filePath: 文件路径
///   - verificationCode: 文件的Hash值
///   - verificationType: Hash类型
///   - completion: 完成回调, 在子线程运行
public static func validateFile(_ filePath: String,
                               code: String,
                               type: FileVerificationType,
                               completion: @escaping (Result<Bool, FileVerificationError>) -> ()) {

日志

Tiercel 3.0 默认提供了完善的日志打印,依靠的是SessionManager的属性var logger: Logable,可以设置sessionManager.logger.option = .none来关闭

SessionManager初始化的时候可以传入一个Logable类型参数,开发者可以遵守Logable协议,自定义一个logger,替代默认的日志打印

public enum LogOption {
    case `default`
    case none
}

public enum LogType {
    case sessionManager(_ message: String, manager: SessionManager)
    case downloadTask(_ message: String, task: DownloadTask)
    case error(_ message: String, error: Error)
}

public protocol Logable {
    var identifier: String { get }
    
    var option: LogOption { get set }
    
    func log(_ type: LogType)
}

错误处理

Tiercel 使用过程中产生的错误,默认都会打印出日志,而跟下载任务相关的错误,大多数都同时保留在task.error里面

进阶用法

SessionManager

SessionManager是下载任务的管理者,管理当前模块所有下载任务

⚠️⚠️⚠️ 按照苹果官方文档的要求,SessionManager实例必须在 App 启动的时候创建,即SessionManager实例的生命周期跟 App 几乎一致,为方便使用,最好是作为AppDelegate的属性,或者是全局变量,具体请参照Demo

/// SessionManager的初始化方法
/// - Parameters:
///   - identifier: 设置SessionManager实例的标识,用于区分不同的下载模块,同时也是URLSession实例的标识,原生级别的后台下载必须要有唯一标识
///   - configuration: SessionManager的配置
///   - logger: 用于该SessionManager实例里所有的日志打印
///   - cache: 用于下载任务的持久化
///   - operationQueue: URLSession的代理执行队列,SessionManager中的所有闭包回调如果没有指定在主线程执行,也会在此队列中执行
public init(_ identifier: String,
            configuration: SessionConfiguration,
            logger: Logable? = nil,
            cache: Cache? = nil,
            operationQueue: DispatchQueue = DispatchQueue(label: "com.Tiercel.SessionManager.operationQueue")) {
    // 实现的代码... 
} 

SessionManager回调的详细说明

// 回调闭包的参数是SessionManager实例,可以得到所有相关的信息
// 所有闭包都可以选择是否在主线程上执行,由onMainQueue参数控制,如果onMainQueue传false,则会在sessionManager初始化时指定的队列上执行,onMainQueue默认为true
// progress 闭包:只要有一个任务正在下载,就会触发
// success 闭包:只有一种情况会触发:
//    所有任务都下载成功(取消和移除的任务会被移除然后销毁,不再被manager管理) ,这时候manager.status == .succeeded
// failure 闭包:只要manager.status != .succeeded,就会触发:
//    1. 调用全部暂停的方法,或者没有等待运行的任务,也没有正在运行的任务,这时候manager.status == .suspended
//    2. 所有任务都结束,但有一个或者多个是失败的,这时候manager.status == .failed
//    3. 调用全部取消的方法,或者剩下一个任务的时候把这个任务取消,这时候manager.status == .canceled
//    4. 调用全部移除的方法,或者剩下一个任务的时候把这个任务移除,这时候manager.status == .removed
sessionManager.progress(onMainQueue: true) { (manager) in
    let progress = manager.progress.fractionCompleted
    print("downloadManager运行中, 总进度:\(progress)")
    }.success { (manager) in
         print("所有下载任务都成功了")
    }.failure { (manager) in
         if manager.status == .suspended {
            print("所有下载任务都暂停了")
        } else if manager.status == .failed {
            print("存在下载失败的任务")
        } else if manager.status == .canceled {
            print("所有下载任务都取消了")
        } else if manager.status == .removed {
            print("所有下载任务都移除了")
        }
}

SessionManager的主要属性

// 用于日志打印
public var logger: Logable
// 是否需要对networkActivityIndicator进行管理
public var isControlNetworkActivityIndicator: Bool
// urlSession的代理回调执行队列,SessionManager中的所有闭包回调如果没有指定在主线程执行,也会在此队列中执行
public let operationQueue: DispatchQueue
// SessionManager的状态
public var status: Status
// SessionManager的缓存管理实例
public let cache: Cache
// SessionManager的标识,区分不同的下载模块
public let identifier: String
// SessionManager的进度
public var progress: Progress
// SessionManager的配置,可以设置请求超时时间,最大并发数,是否允许蜂窝网络下载等
public var configuration: SessionConfiguration
// 所有下载中的任务加起来的总速度
public var speed: Int64
public var speedString: String
// 所有下载中的任务需要的剩余时间
public var timeRemaining
public var timeRemainingString: String
// SessionManager管理的下载任务,取消和移除的任务会被销毁,但操作是异步的,在回调闭包里面获取才能保证正确
public var tasks: [Task]

可以通过以下方法进行排序

// 改变单个任务的位置
public func moveTask(at sourceIndex: Int, to destinationIndex: Int) {
    // ...
}

// 对所有任务进行排序
public func tasksSort(by areInIncreasingOrder: (DownloadTask, DownloadTask) throws -> Bool) rethrows {
    // ...
}

SessionConfiguration

SessionConfiguration是 Tiercel 中配置SessionManager的结构体,可配置属性如下:

// 请求超时时间
public var timeoutIntervalForRequest

// 最大并发数
// 支持后台下载的任务,系统会进行最大并发数限制
// 在iOS 11及以上是6,iOS 11以下是3
public var maxConcurrentTasksLimit

// 是否允许蜂窝网络下载
public var allowsCellularAccess = false

// 昂贵网络访问限制,只能在iOS 13及以上系统使用
public var allowsExpensiveNetworkAccess: Bool = true

// 低数据网络访问限制,只能在iOS 13及以上系统使用
public var allowsConstrainedNetworkAccess: Bool = true

更改SessionManager的配置

// 无论是否有下载任务正在运行,都可以更改SessionManager配置
// 如果只是更改某一项,可以直接对SessionManager属性设置
sessionManager.configuration.allowsCellularAccess = true

// 如果是需要更改多项,需要重新创建SessionConfiguration,再进行赋值
let configuration = SessionConfiguration()
configuration.allowsCellularAccess = true
configuration.maxConcurrentTasksLimit = 2
configuration.timeoutIntervalForRequest = 60

sessionManager.configuration = configuration

注意:建议在SessionManager初始化的时候传入已经修改好的SessionConfiguration实例,参考 Demo。Tiercel 也支持在任务下载中修改配置,但是不建议修改configuration后马上开启任务下载,即不要在同一个代码块里修改configuration后开启任务下载,这样很容易造成错误。

// 不要这样操作
sessionManager.configuration.allowsCellularAccess = true
let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg")

如果实在需要进行这种操作,请修改configuration后,设置1秒以上的延迟再开启任务下载。

// 如果实在需要,请延迟开启任务
sessionManager.configuration.allowsCellularAccess = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    let task = sessionManager.download("http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.2.4.dmg")
}

DownloadTask

DownloadTask是 Tiercel 中的下载任务类型,继承自Task<Type>DownloadTask实例都是由SessionManager实例创建,不能单独创建

主要属性

// 保存到沙盒的下载文件的文件名,如果在下载的时候没有设置,则默认为url的md5加上文件扩展名
public var fileName: String
// 下载任务对应的url
public let url: URL
// 下载任务的状态
public var status: Status
// 下载文件的校验状态
public var validation: Validation
// 下载任务的进度
public var progress: Progress = Progress()
// 下载任务的开始日期
public var startDate: TimeInterval
public var startDateString: String	
// 下载任务的结束日期
public var endDate: TimeInterval
public var endDateString: String
// 下载任务的速度
public var speed: Int64
public var speedString: String
// 下载任务的剩余时间
public var timeRemaining: Int64 = 0
public var timeRemainingString: String
// 下载文件路径
public var filePath: String
// 下载文件的扩展名
public var pathExtension: String?
// 下载任务失败的error
public  var error: Error?

对下载任务操作,是通过SessionManager实例进行的:

  • 开启
  • 暂停
  • 取消,没有完成的任务从SessionManager实例中的tasks中移除,不保留缓存,已经下载完成的任务不受影响
  • 移除,已经完成的任务也会被移除,没有下载完成的缓存文件会被删除,已经下载完成的文件可以选择是否保留

注意:对下载中的任务进行暂停、取消和移除操作,是异步进行的,这些操作的 API 都有提供回调闭包,在回调闭包里面获取状态才能保证正确

Cache

Cache是 Tiercel 中负责管理缓存下载任务信息和下载文件的类。Cache实例一般作为SessionManager实例的属性来使用。

/// 初始化方法
/// - Parameters:
///   - identifier: 不同的identifier代表不同的下载模块。如果没有自定义下载目录,Cache会提供默认的目录,这些目录跟identifier相关
///   - downloadPath: 存放用于DownloadTask持久化的数据,默认提供的downloadTmpPath、downloadFilePath也是在里面
///   - downloadTmpPath: 存放下载中的临时文件
///   - downloadFilePath: 存放下载完成后的文件
public init(_ identifier: String,
            downloadPath: String? = nil,
            downloadTmpPath: String? = nil,
            downloadFilePath: String? = nil) {
 	// ...   
}

SessionManager初始化的时候可以传入一个Cache类型参数,开发者可以自定义一个Cache,改变缓存目录的位置

主要属性

// 下载模块的目录路径
public let downloadPath: String

// 没有完成的下载文件缓存的目录路径
public let downloadTmpPath: String

// 下载完成的文件的目录路径
public let downloadFilePath: String

对外暴露的 API:

// 传入文件名来返回String、URL,或者判断文件是否存在
public func filePath(fileName: String) -> String?
public func fileURL(fileName: String) -> URL? 
public func fileExists(fileName: String) -> Bool 

// 传入url来返回String、URL,或者判断文件是否存在
public func filePath(url: URLConvertible) -> String? 
public func fileURL(url: URLConvertible) -> URL? 
public func fileExists(url: URLConvertible) -> Bool 

// 清除磁盘缓存
public func clearDiskCache(onMainQueue: Bool = true, handler: Handler<Cache>? = nil)