Shallows 0.10.0

Shallows 0.10.0

Oleg Dreyman 维护。



Shallows 0.10.0

Shallows

Swift Platform

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>

存储实例有 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)
    }
}

转换

键和值可以进行映射

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(_:)

存储组成

浅层的另一个核心概念是组合。每次请求一个图像都去访问磁盘操作可能既缓慢又不高效。相反,您可以将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可用的组合方法:

  • .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>) -> ())

其中 KeyValue 是相关类型。

注意:请注意,您需对自己的实现负责线程安全。通常 retrieveset 不会从主线程调用,因此您应确保不会发生竞争条件。

要将其作为 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"),
]