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 = try? result.get() { print(city) }
}
// or
let city = try await diskStorage.retrieve(forKey: "kharkiv")
Shallows 的主要类型是 Storage<Key, Value>
。它是一个抽象的、类型擦除的结构,不包含任何逻辑 —— 它需要被提供逻辑。最基本的是 MemoryStorage
let storage = MemoryStorage<String, Int>().asStorage() // Storage<String, Int>
Storage 实例有 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)
}
}
Key 和 Value 可以被映射
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 /* .. */ })
注意: 在值为 Data
的 Storage
上定义了几个方便的方法: .mapString(withEncoding:)
, .mapJSON()
, .mapJSONDictionary()
, .mapJSONObject(_:)
.mapPlist(format:)
, .mapPlistDictionary(format:)
, .mapPlistObject(_:)
。
Shallows 的另一个核心概念是组合。每次请求图像都访问磁盘可能会很慢且效率低下。相反,您可以组合 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:)
(参见 Storage 的组合).backed(by:)
的工作方式与 combined(with:)
相同,但它不会将值推送到后备存储.pushing(to:)
不会从后备存储中检索值,但会在 set
时推送到它可用于 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) = try? result.get() {
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
将使用 []
的默认值,而不是直接使整个更新失败。
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, Error>) -> ())
func set(_ value: Value, forKey key: Key, completion: @escaping (Result<Void, Error>) -> ())
其中 Key
和 Value
是关联类型。
注意: 请注意,您有责任保证实现的线程安全性。通常,不会从主线程调用 retrieve
和 set
,因此您应确保不会发生竞争条件。
要将其用作 Storage<Key, Value>
实例,只需在其上调用 .asStorage()
let storage = MyStorage().asStorage()
您也可以仅遵循 ReadOnlyStorageProtocol
。这样,您只需要定义一个 retrieve(forKey:completion:)
方法。
从 Xcode 11 开始,Shallows 仅通过 Swift Package Manager 官方提供。
在 Xcode 11 或更高版本中,在您的项目中,选择:File > Swift Packages > Add Package Dependency
在搜索栏中键入
https://github.com/dreymonde/Shallows
然后继续安装。
如果您在 Swift Packages 面板中找不到任何内容,则可能尚未添加您的 github 帐户。您可以在 Xcode 的Preferences 面板中的 Accounts 部分中进行添加。
对于基于命令行的应用程序,您可以直接将其添加到您的 Package.swift 文件中
dependencies: [
.package(url: "https://github.com/dreymonde/Shallows", from: "0.13.0"),
]
当然,您始终可以选择直接复制粘贴代码。
支持 Carthage 和 Cocoapods 的最后一个 Shallows 版本是 0.10.0。 Carthage 和 Cocoapods 将不再受到官方支持。
Carthage
github "dreymonde/Shallows" ~> 0.10.0
Cocoapods
pod 'Shallows', '~> 0.10.0'