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 = 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 实例有 retrieveset 方法,它们是异步且可能失败的

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 /* .. */ })

注意: 在值为 DataStorage 上定义了几个方便的方法: .mapString(withEncoding:), .mapJSON(), .mapJSONDictionary(), .mapJSONObject(_:) .mapPlist(format:), .mapPlistDictionary(format:), .mapPlistObject(_:)

Storage 的组合

Shallows 的另一个核心概念是组合。每次请求图像都访问磁盘可能会很慢且效率低下。相反,您可以组合 MemoryStorageFileSystemStorage

let efficient = MemoryStorage<Filename, UIImage>().combined(with: imageStorage)

它做了以下几件事:

  1. 当尝试检索图像时,首先会检查内存存储,如果它不包含值,则会向磁盘存储发出请求。
  2. 如果磁盘存储存储了一个值,它将被拉取到内存存储并返回给用户。
  3. 设置图像时,它将被同时设置到内存和磁盘存储中。

只读存储

如果您不想暴露对存储的写入操作,您可以将其设为只读存储

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 的组合:

可用于 ReadOnlyStorage 的组合:

可用于 WriteOnlyStorage 的组合:

单元素存储

您可以拥有键为 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 将使用 [] 的默认值,而不是直接使整个更新失败。

使用 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, Error>) -> ())
func set(_ value: Value, forKey key: Key, completion: @escaping (Result<Void, Error>) -> ())

其中 KeyValue 是关联类型。

注意: 请注意,您有责任保证实现的线程安全性。通常,不会从主线程调用 retrieveset,因此您应确保不会发生竞争条件。

要将其用作 Storage<Key, Value> 实例,只需在其上调用 .asStorage()

let storage = MyStorage().asStorage()

您也可以仅遵循 ReadOnlyStorageProtocol。这样,您只需要定义一个 retrieve(forKey:completion:) 方法。

安装

Swift Package Manager

从 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"),
]

手动

当然,您始终可以选择直接复制粘贴代码。

已弃用的依赖项管理器

支持 CarthageCocoapods 的最后一个 Shallows 版本是 0.10.0。 Carthage 和 Cocoapods 将不再受到官方支持。

Carthage

github "dreymonde/Shallows" ~> 0.10.0

Cocoapods

pod 'Shallows', '~> 0.10.0'