05、hyperoslo cache学习
描述
在这个领域里,Cache并不是独一无二的,但它并不是另一个让你拥有上帝力量的怪物库。
除了缓存,它什么都不做,但它做得很好。
它提供了一个良好的公共 API,它有开箱即用的实现和巨大的自定义可能性。
高速缓存在 Swift 4 中使用Codable来执行序列化。
关键特性
- 与 Swift 4 Codable合作。任何符合Codable的东西都将被Storage轻松地保存和加载。
- 默认磁盘存储。可选地使用内存存储来启用混合.
- 选项通过DiskConfig和MemoryConfig.
- 支持过期和清除过期的对象.
- 线程安全的。可以从任何队列访问操作.
- 默认情况下同步。还支持异步 APIs.
- 存储图像通过ImageWrapper.
- 广泛的单元测试覆盖面和文档.
- 支持 iOS, tvOS 和 macOS.
使用
Storage
Cache
是建立基于责任链模式,在其中有许多处理对象,每个处理对象都知道如何执行 1 个任务,并委托给下一个任务。
但这只是实现细节。您需要知道的是Storage
,它保存并加载Codable
对象。
Storage
有磁盘存储和可选的内存存储。
内存存储应该更少时间和内存消耗,而磁盘存储用于满足应用程序生命周期的内容,更像一种存储用户信息的方便方式,这些信息应该在应用程序启动时持久化。
DiskConfig
是必需的,设置磁盘存储。您可以选择通过MemoryConfig
来将内存作为前端存储。
let diskConfig = DiskConfig(name: "Floppy")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)
let storage = try? Storage(diskConfig: diskConfig, memoryConfig: memoryConfig)
Codable 类型
Storage
支持任何符合Codable
协议对象。
你可以做你自己的事情符合 Codable
,这样可以保存和加载Storage
。
支持的类型:
- 原语如 Int, Float, String, Bool, …
- 数组的元素如 [Int], [Float], [Double], …
- 基本类型的 Set 如 Set, Set, …
- 简单的字典 [String: Int], [String: String], …
- Date
- URL
- Data
错误处理
错误处理是通过try catch完成的。Storage在StorageError方面抛出错误。
public enum : Error {
case notFound
/// 对象被找到,但是被请求的类型失败了
case typeNotMatch
/// 文件属性是畸形的
case malformedFileAttributes
/// 不能执行解码
case decodingFailed
/// 不能执行编码
case encodingFailed
/// 存储已经被释放了
case deallocated
}
在从存储中加载时,可能会出现磁盘问题或类型不匹配的错误,因此,如果要处理错误,则需要try catch
。
do {
let storage = try Storage(diskConfig: diskConfig, memoryConfig: memoryConfig)
} catch {
print(error)
}
配置
下面是如何使用多种配置选项
let diskConfig = DiskConfig(
//磁盘存储的名称,这将用作目录中的文件夹名称
name: "Floppy",
//在默认情况下,每个添加的对象都将使用该过期日期
//如果它没有在`setObject(forKey:expiry:)`方法中被覆盖
expiry: .date(Date().addingTimeInterval(2*3600)),
maxSize: 10000,
// 存储磁盘缓存的位置。如果nil,它被放置在cachesDirectory目录中.
directory: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
appropriateFor: nil, create: true).appendingPathComponent("MyPreferences"),
// 数据保护用于在磁盘上以加密格式存储文件,并根据需要对其进行解密
protectionType: .complete
)
let memoryConfig = MemoryConfig(
//在默认情况下,每个添加的对象都将使用该过期日期
//如果它没有在`setObject(forKey:expiry:)`方法中被覆盖
expiry: .date(Date().addingTimeInterval(2*60)),
/// 内存中的对象的最大数量应该保持在内存中
countLimit: 50,
/// 缓存在开始清除对象之前所能容纳的最大总成本
totalCostLimit: 0
)
在 iOS 平台上,tvOS 还可以在DiskConfig
上指定protectionType
,在应用程序的容器中为存储在磁盘上的文件添加安全级别。
有关更多信息,请参见FileProtectionType
同步 APIs
缺省情况下,Storage
是同步的,是线程安全的,您可以从任何队列访问它。
所有同步功能都受到StorageAware
协议的约束。
// 保存到storage
try? storage.setObject(10, forKey: "score")
try? storage.setObject("Oslo", forKey: "my favorite city", expiry: .never)
try? storage.setObject(["alert", "sounds", "badge"], forKey: "notifications")
try? storage.setObject(data, forKey: "a bunch of bytes")
try? storage.setObject(authorizeURL, forKey: "authorization URL")
// 从storage加载
let score = try? storage.object(ofType: Int.self, forKey: "score")
let favoriteCharacter = try? storage.object(ofType: String.self, forKey: "my favorite city")
// 检查对象是否存在
let hasFavoriteCharacter = try? storage.existsObject(ofType: String.self, forKey: "my favorite city")
// 删除存储中的对象
try? storage.removeObject(forKey: "my favorite city")
// 删除所有对象
try? storage.removeAll()
// 删除过期的对象
try? storage.removeExpiredObjects()
Entry
您可以使用它的过期信息和元数据来获取对象。您可以使用 Entry
let entry = try? storage.entry(ofType: String.self, forKey: "my favorite city")
print(entry?.object)
print(entry?.expiry)
print(entry?.meta)
如果从磁盘存储器中取出对象,meta可能包含文件信息。
自定义 Codable
Codable
适用于像[String: Int]
, [String: String]
, … 这样的简单字典
它并不适用于[String: Any]
,因为Any不符合Codable的一致性,它将在运行时引发致命错误。因此,当您从后端响应中获得 json 时,您需要将其转换为您的自定义Codable对象,并将其保存为存储。
struct User: Codable {
let firstName: String
let lastName: String
}
let user = User(fistName: "John", lastName: "Snow")
try? storage.setObject(user, forKey: "character")
异步 APIs
在async
方式中,您处理Result
而不是try catch
,因为 result
是在稍后的时间交付的,以避免阻塞当前的调用队列。在完成块中,您要么有value
,要么有error
。
您可以通过storage.async
访问异步 APIs,它也是线程安全的,您可以按照您想要的任何顺序使用同步和异步 APIs。所有的异步函数都受到AsyncStorageAware
协议的约束。
storage.async.setObject("Oslo", forKey: "my favorite city") { result in
switch result {
case .value:
print("saved successfully")
case .error(let error):
print(error)
}
}
}
storage.async.object(ofType: String.self, forKey: "my favorite city") { result in
switch result {
case .value(let city):
print("my favorite city is (city)")
case .error(let error):
print(error)
}
}
}
storage.async.existsObject(ofType: String.self, forKey: "my favorite city") { result in
if case .value(let exists) = result, exists {
print("I have a favorite city")
}
}
storage.async.removeAll() { result in
print("removal completes")
}
storage.async.removeExpiredObjects() { result in
print("removal completes")
}
到期日期
默认情况下,所有保存的对象与您在DiskConfig或MemoryConfig中指定的到期时间具有相同的过期时间。可以通过指定setObject的expiry来覆盖特定对象。
// 配置的默认过期日期将被应用到项目中
try? storage.setObject("This is a string", forKey: "string")
// 给定的过期日期
try? storage.setObject(
"This is a string",
forKey: "string"
expiry: .date(Date().addingTimeInterval(2 * 3600))
)
// 清除过期的对象
storage.removeExpiredObjects()
关于图片?
如您所知,NSImage和UIImage在默认情况下不符合Codable。
为了使它在可编程序中运行良好,我们引入了ImageWrapper,这样您就可以保存和加载图像
let wrapper = ImageWrapper(image: starIconImage)
try? storage.setObject(wrapper, forKey: "star")
let icon = try? storage.object(ofType: ImageWrapper.self, forKey: "star").image
如果你想把图像载入 UIImageView 或 NSImageView,那么我们也有一个很好的礼物给你。叫做Imaginary,并使用Cache下罩时,让你的生活更容易处理远程图像。
处理 JSON 响应
大多数情况下,我们的用例是从后端获取 json,并将 json 存储到存储中,以便将来使用。如果你使用库Alamofire或Malibu,你是得到 json 形式的字典,字符串,或数据。
Storage可以持久存储字符串或数据。您甚至可以使用JSONArrayWrapper和JSONDictionaryWrapper将 json 保存到Storage中,但是我们更喜欢持久化强类型的对象,因为这些对象是您将在 UI 中显示的对象。此外,如果 json 数据不能转换为强类型的对象,那么保存它的意义是什么呢?😉
您可以在JSONDecoder上使用这些扩展来解码 json 字典、字符串或数据到对象。
let user = JSONDecoder.decode(jsonString, to: User.self)
let cities = JSONDecoder.decode(jsonDictionary, to: [City].self)
let dragons = JSONDecoder.decode(jsonData, to: [Dragon].self)
这就是如何使用Alamofire执行对象转换和保存。
Alamofire.request("https://gameofthrones.org/mostFavoriteCharacter").responseString { response in
do {
let user = try JSONDecoder.decode(response.result.value, to: User.self)
try storage.setObject(user, forKey: "most favorite character")
} catch {
print(error)
}
}