这是我参与更文挑战的第12天,活动详情查看: 更文挑战
本文为在阅读 Kingfisher 源码时的收货。
Kingfisher 源码阅读笔记(1) 的续篇。
多线程处理缓存数据
Kingfisher 在判断本地是否已经存在图片的缓存文件时,使用了 maybeCached
保存了当前已经存在的文件路径。在修改 maybeCached 时使用了专门的队列 maybeCachedCheckingQueue
。
var maybeCached : Set<String>?
let maybeCachedCheckingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.maybeCachedCheckingQueue")
// 添加缓存内容时使用异步操作
maybeCachedCheckingQueue.async {
self.maybeCached?.insert(fileURL.lastPathComponent)
}
// 获取缓存内容时使用同步操作
let fileMaybeCached = maybeCachedCheckingQueue.sync {
return maybeCached?.contains(fileURL.lastPathComponent) ?? true
}
在处理不牵扯到 UI 更改的缓存数据时,可以考虑使用专门的自定义串行队列来获取、修改缓存数据,提供性能。
另外,每次读取缓存文件信息之后,都要更新文件的最后访问时间,这个操作也放到了单独的队列中。
let metaChangingQueue: DispatchQueue
metaChangingQueue = DispatchQueue(label: creation.cacheName)
metaChangingQueue.async {
meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration)
}
异常的处理
Kingfisher 将所有的可能抛出的异常全部定义在了 KingfisherError
中,每个成员(case)表示一类错误,具体的错误信息放在 case 关联的 reason 中。
public enum KingfisherError: Error {
public enum RequestErrorReason {
// 具体错误
}
public enum ResponseErrorReason {
// 具体错误
}
// 其他具体错误
// MARK: Member Cases
case requestError(reason: RequestErrorReason)
case responseError(reason: ResponseErrorReason)
// 其他错误类型...
// 其他方法
}
在发生异常时,都会转化成 KingfisherError
对应的异常,具体的底层异常也会携带到错误信息当中。比如,在将图片数据存储到文件时,发生的异常时,会发出 cacheError
类型的异常,详细的信息为 cannotCreateCacheFile
,更底层的错误放在了关联的 error 中。
do {
try data.write(to: fileURL)
} catch {
throw KingfisherError.cacheError(
reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
)
}
在制作 framework 时,可以参考这种方式:
- 将所有可能抛出的异常进行归类,集中在了一处,使用者对可能抛出的异常一目了然;
- 将底层的异常封装成更抽象的异常,同时保留底层的异常,而不是直接向上抛异常。
另外,Kingfisher 也考虑到了 Objectiv-C 中的 NSError。Swift 中的 Error 想要完整的转化为 Objectiv-C 中的 NSError 就需要实现 CustomNSError
协议。相关信息可以查看「没故事的卓同学」的文章Swift 3必看:Error与NSError的关系。
打破一切外部可能造成的循环引用
当需要使用外部传入闭包时,要想尽一切办法消除存在循环引用。比如下面代码:Backend
要强引用 config
,当使用完 config
的 cachePathBlock
闭包后,要及时的清除。
public class Backend<T: DataTransformable> {
public var config: Config
init(noThrowConfig config: Config, creatingDirectory: Bool) {
var config = config
// `config` 的 `cachePathBlock` 闭包生成 directoryURL
self.directoryURL = creation.cachePathBlock("", "")
// Break any possible retain cycle set by outside.
config.cachePathBlock = nil
self.config = config
}
}
config
的 cachePathBlock
属性专门设计为隐式解析可选类型:
public struct Config {
var cachePathBlock: ((_ directory: URL, _ cacheName: String) -> URL)! = {
(directory, cacheName) in
return directory.appendingPathComponent(cacheName, isDirectory: true)
}
}