Shallows
Shallows 是在轻量级数据存储和持久化上的一个通用抽象层。它提供了一种 Storage<Key, Value>
类型,其实例可以轻松地进行转换和组合。它使您能够创建高度复杂、有效和可靠的缓存/持久化解决方案。
Shallows 深受 Carlos 和 Brandon Kase 的这个精彩演讲 (点击观看) 的启发。
Shallows 是一个真正的小型、基于组件的项目,所以如果您需要更多可控的解决方案 – 自己构建一个!我们的源代码旨在提供帮助。
使用
struct City : Codable {
let name: String
let foundationYear: Int
}
let diskStorage = DiskStorage.main.folder("cities", in: .cachesDirectory)
.mapJSONObject(City.self) // Storage<Filename, City>
let kharkiv = City(name: "Kharkiv", foundationYear: 1654)
diskStorage.set(kharkiv, forKey: "kharkiv")
diskStorage.retrieve(forKey: "kharkiv") { (result) in
if let city = result.value { print(city) }
}
指南
Shallows 的主要类型是 Storage<Key, Value>
。它是一个抽象、类型 erased 结构,不包含任何逻辑 —— 需要提供给它。最基本的一个是 MemoryStorage
let storage = MemoryStorage<String, Int>().asStorage() // Storage<String, Int>
存储实例有 retrieve
和 set
方法,它们是异步的和可能失败的操作
storage.retrieve(forKey: "some-key") { (result) in
switch result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
storage.set(10, forKey: "some-key") { (result) in
switch result {
case .success:
print("Value set!")
case .failure(let error):
print(error)
}
}
转换
键和值可以进行映射
let storage = DiskStorage.main.folder("images", in: .cachesDirectory) // Storage<Filename, Data>
let images = storage
.mapValues(to: UIImage.self,
transformIn: { data in try UIImage.init(data: data).unwrap() },
transformOut: { image in try UIImagePNGRepresentation(image).unwrap() }) // Storage<Filename, UIImage>
enum ImageKeys : String {
case kitten, puppy, fish
}
let keyedImages = images
.usingStringKeys()
.mapKeys(toRawRepresentableType: ImageKeys.self) // Storage<ImageKeys, UIImage>
keyedImages.retrieve(forKey: .kitten, completion: { result in /* .. */ })
请注意:在 Storage
上定义了几个方便的方法,用于处理值,例如 .mapString(withEncoding:)
、.mapJSON()
、.mapJSONDictionary()
、.mapJSONObject(_:)
、.mapPlist(format:)
、.mapPlistDictionary(format:)
和 .mapPlistObject(_:)
。
存储组成
浅层的另一个核心概念是组合。每次请求一个图像都去访问磁盘操作可能既缓慢又不高效。相反,您可以将MemoryStorage
和FileSystemStorage
组合起来。
let efficient = MemoryStorage<Filename, UIImage>().combined(with: imageStorage)
它做了几件事情
- 尝试检索图像时,内存存储将首先被检查,如果它不包含值,则请求将转移到磁盘存储。
- 如果磁盘存储存储了值,则它将移动到内存存储并返回给用户。
- 设置图像时,它将同时设置到内存和磁盘存储。
只读存储
如果您不希望将写入操作暴露给存储,您可以将其作为只读存储。
let readOnly = storage.asReadOnlyStorage() // ReadOnlyStorage<Key, Value>
只读存储也可以进行映射和组合。
let immutableFileStorage = DiskStorage.main.folder("immutable", in: .applicationSupportDirectory)
.mapString(withEncoding: .utf8)
.asReadOnlyStorage()
let storage = MemoryStorage<Filename, String>()
.backed(by: immutableFileStorage)
.asReadOnlyStorage() // ReadOnlyStorage<Filename, String>
只写存储
类似地,也有只写存储可用。
let writeOnly = storage.asWriteOnlyStorage() // WriteOnlyStorage<Key, Value>
不同的组合方式
Storage
可用的组合方法:
.combined(with:)
(参见存储组成).backed(by:)
将与combined(with:)
相同,但它不会将值推送到后端存储.pushing(to:)
不会从后端存储检索值,但在设置时会推送到它
ReadOnlyStorage
可用的组合方法:
.backed(by:)
WriteOnlyStorage
可用的组合方法:
.pushing(to:)
单元素存储
您可以有一个具有键 Void
的存储。这意味着您可以在此处存储一个元素。 Shallows 提供了一个方便的 .singleKey
方法来创建它
let settings = DiskStorage.main.folder("settings", in: .applicationSupportDirectory)
.mapJSONDictionary()
.singleKey("settings") // Storage<Void, [String : Any]>
settings.retrieve { (result) in
// ...
}
同步存储
Shallows 的存储默认为异步设计。然而,在某些情况下(例如,在脚本或测试中),拥有同步存储可能会有用。您可以通过对其调用 .makeSyncStorage()
使任何存储同步
let strings = DiskStorage.main.folder("strings", in: .cachesDirectory)
.mapString(withEncoding: .utf8)
.makeSyncStorage() // SyncStorage<Filename, String>
let existing = try strings.retrieve(forKey: "hello")
try strings.set(existing.uppercased(), forKey: "hello")
键的值更新
Shallows 在存储上提供了一个方便的 .update
方法
let arrays = MemoryStorage<String, [Int]>()
arrays.update(forKey: "some-key", { $0.append(10) })
存储组合
组合是 Shallows 的一个非常强大的功能。它允许您以这样的方式组合您的存储,即仅当您的请求两个存储都完成时才会获取结果。例如
let strings = MemoryStorage<String, String>()
let numbers = MemoryStorage<String, Int>()
let zipped = zip(strings, numbers) // Storage<String, (String, Int)>
zipped.retrieve(forKey: "some-key") { (result) in
if let (string, number) = result.value {
print(string)
print(number)
}
}
zipped.set(("shallows", 3), forKey: "another-key")
这不是很酷吗?
从错误中恢复
您可以使用 fallback(with:)
或 defaulting(to:)
方法来保护您的存储实例免受故障的影响
let storage = MemoryStorage<String, Int>()
let protected = storage.fallback(with: { error in
switch error {
case MemoryStorageError.noValue:
return 15
default:
return -1
}
})
let storage = MemoryStorage<String, Int>()
let defaulted = storage.defaulting(to: -1)
这在使用 update
方法时特别有用
let storage = MemoryStorage<String, [Int]>()
storage.defaulting(to: []).update(forKey: "first", { $0.append(10) })
这意味着如果在检索现有值时失败,update
将使用默认值 []
而不是使整个更新失败。
NSCacheStorage
使用 NSCache
是一个复杂的类:它只支持引用类型,因此您被迫使用,例如,NSData
而不是 Data
等等。为了帮助您,《Shallows》提供了一套针对传统 Foundation 类型的便捷扩展。
let nscache = NSCacheStorage<NSURL, NSData>()
.toNonObjCKeys()
.toNonObjCValues() // Storage<URL, Data>
创建自己的存储
要创建自己的缓存层,您应该遵从 StorageProtocol
。这意味着您应该定义以下两个方法
func retrieve(forKey key: Key, completion: @escaping (Result<Value>) -> ())
func set(_ value: Value, forKey key: Key, completion: @escaping (Result<Void>) -> ())
其中 Key
和 Value
是相关类型。
注意:请注意,您需对自己的实现负责线程安全。通常 retrieve
和 set
不会从主线程调用,因此您应确保不会发生竞争条件。
要将其作为 Storage<Key, Value>
实例使用,只需调用它上的 .asStorage()
即可
let storage = MyStorage().asStorage()
您也可以仅遵守 ReadOnlyStorageProtocol
。这样,您只需定义一个 retrieve(forKey:completion:)
方法。
安装
Shallows 通过 Carthage 提供。要安装,只需在 Cartfile 中写入以下内容
github "dreymonde/Shallows" ~> 0.9.0
Shallows 还可以通过 CocoaPods 获取。
pod 'Shallows', '~> 0.9.0'
以及 Swift Package Manager
dependencies: [
.package(url: "https://github.com/dreymonde/Shallows.git", from: "0.9.0"),
]