缓存 6.0.0

Cache 6.0.0

测试已测试
Lang语言 SwiftSwift
许可证 NOASSERTION
发布最新发布2020年10月
SPM支持SPM

Vadym MarkovChristoffer WinterkvistHyper Interaktiv ASElvis NuñezKhoa Pham维护。



Cache 6.0.0

Cache

CI Status Version Carthage Compatible License Platform Documentation Swift

目录

描述

Cache Icon

Cache 并非在此领域中独一无二,但它也不是另一个提供神力的怪物库。Cache 只做缓存,但做得很好。它提供了一种良好的公共 API,带有原生的实现和出色的自定义选项。Cache 使用 Swift 4 中的 Codable 来执行序列化。

在这里阅读故事 开源故事:从 Cachable 到 Cache 中的泛型存储

主要特点

  • 与 Swift 4 Codable 协作。任何遵循 Codable 的内容都将通过 Storage 容易地保存和加载。
  • 内存和磁盘存储的混合。
  • 通过 DiskConfigMemoryConfig 提供多种选项。
  • 支持 expiry 和过期对象清理。
  • 线程安全。操作可以从任何队列访问。
  • 默认同步。也支持异步 API。
  • 广泛覆盖单元测试和优秀的文档。
  • 支持 iOS、tvOS 和 macOS。

使用方法

存储

Cache 是基于 责任链模式构建的,其中有很多处理对象,每个对象都知道如何执行 1 个任务并将其委托给下一个对象,因此您可以按照自己的喜好组合存储。

目前支持以下存储类型:

  • MemoryStorage:将对象保存到内存中。
  • DiskStorage:将对象保存到磁盘。
  • HybridStorage:将对象保存到内存和磁盘,因此您可以在磁盘上获得持久化的对象,同时快速访问内存中的对象。
  • SyncStorage:阻塞 API,所有读写操作都安排在一个序列队列中,以同步方式执行。
  • AsyncStorage:非阻塞 API,操作安排在内部队列中以串行方式处理。不得同时进行读写操作。

虽然您可以选择使用这些存储,但不必这样做。因为我们还提供了一个便利的 Storage,它使用底层的 HybridStorage,并通过 SyncStorageAsyncStorage 提供同步和异步 API。

您只需指定使用 DiskConfigMemoryConfig 要配置的内容即可。默认配置就可以良好地运行,但您可以进行很多自定义。

let diskConfig = DiskConfig(name: "Floppy")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)

let storage = try? Storage(
  diskConfig: diskConfig,
  memoryConfig: memoryConfig,
  transformer: TransformerFactory.forCodable(ofType: User.self) // Storage<User>
)

泛型,类型安全和转换器

现在所有 Storage 都默认为泛型,因此您可以获得类型安全体验。一旦创建了一个存储,它就有了类型约束,之后您就不需要为每个操作指定类型。

如果您想更改类型,Cache 提供了 transform 函数,查找内置转换器的 TransformerTransformerFactory

let storage: Storage<User> = ...
storage.setObject(superman, forKey: "user")

let imageStorage = storage.transformImage() // Storage<UIImage>
imageStorage.setObject(image, forKey: "image")

let stringStorage = storage.transformCodable(ofType: String.self) // Storage<String>
stringStorage.setObject("hello world", forKey: "string")

每次转换都允许您使用特定类型,然而底层的缓存机制保持不变,您正在处理的仍然是同一个 Storage,只是使用了不同的类型注解。如果您想的话,也可以为每个类型创建不同的 Storage

Transformer 功能是必要的,因为它需要将对象序列化和反序列化到 Data 以实现磁盘持久化。 CacheDataCodableUIImage/NSImage 提供默认的 Transformer

可编码类型

存储库(Storage)支持遵循 Codable 协议的任何对象。您可以 使自己的类遵循 Codable 以使其能够从存储库中保存和加载数据。

支持以下类型:

  • 基本类型,如 IntFloatStringBool 等。
  • 原始类型的数组,如 [Int][Float][Double] 等。
  • 原始类型集合,如 SetSet 等。
  • 简单的字典,如 [String: Int][String: String] 等。
  • 日期
  • URL
  • 数据

错误处理

错误处理通过 try catch 来完成。存储库通过 StorageError 抛出错误。

public enum StorageError: Error {
  /// Object can not be found
  case notFound
  /// Object is found, but casting to requested type failed
  case typeNotMatch
  /// The file attributes are malformed
  case malformedFileAttributes
  /// Can't perform Decode
  case decodingFailed
  /// Can't perform Encode
  case encodingFailed
  /// The storage has been deallocated
  case deallocated
  /// Fail to perform transformation to or from Data
  case transformerFail
}

可能存在由于磁盘问题或类型不匹配导致的错误,因此在存储时想处理错误,需要进行 \(\text{try catch}\) 操作。

do {
  let storage = try Storage(diskConfig: diskConfig, memoryConfig: memoryConfig)
} catch {
  print(error)
}

配置

以下是您可以调整的众多配置选项之一

let diskConfig = DiskConfig(
  // The name of disk storage, this will be used as folder name within directory
  name: "Floppy",
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*3600)),
  // Maximum size of the disk cache storage (in bytes)
  maxSize: 10000,
  // Where to store the disk cache. If nil, it is placed in `cachesDirectory` directory.
  directory: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
    appropriateFor: nil, create: true).appendingPathComponent("MyPreferences"),
  // Data protection is used to store files in an encrypted format on disk and to decrypt them on demand
  protectionType: .complete
)
let memoryConfig = MemoryConfig(
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*60)),
  /// The maximum number of objects in memory the cache should hold
  countLimit: 50,
  /// The maximum total cost that the cache can hold before it starts evicting objects
  totalCostLimit: 0
)

在 iOS 和 tvOS 上,我们还可以在 DiskConfig 上指定 protectionType,以向应用程序容器中存储的文件增加一层安全性。有关更多信息,请参阅 FileProtectionType

同步 API

存储库默认是同步的,并且是 线程安全 的,您可以从任何队列访问它。所有同步函数都受制于 StorageAware 协议。

// Save to 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")

// Load from storage
let score = try? storage.object(forKey: "score")
let favoriteCharacter = try? storage.object(forKey: "my favorite city")

// Check if an object exists
let hasFavoriteCharacter = try? storage.existsObject(forKey: "my favorite city")

// Remove an object in storage
try? storage.removeObject(forKey: "my favorite city")

// Remove all objects
try? storage.removeAll()

// Remove expired objects
try? storage.removeExpiredObjects()

入口

有时候您想要获取对象及其过期信息和元数据。您可以使用 入口

let entry = try? storage.entry(forKey: "my favorite city")
print(entry?.object)
print(entry?.expiry)
print(entry?.meta)

meta 可能包含文件信息,如果对象是从磁盘存储中检索的。

自定义编码

Codable 对简单的字典如 [String: Int][String: String],... 有效。它不能用于 [String: Any],因为 Any 不遵守 Codable 协议,这将在运行时引发 致命错误。所以当您从后端响应中获取 json 时,您需要将其转换为您的自定义 Codable 对象,并将其保存到 Storage 中。

struct User: Codable {
  let firstName: String
  let lastName: String
}

let user = User(fistName: "John", lastName: "Snow")
try? storage.setObject(user, forKey: "character")

异步API

在异步方式中,您处理 Result 而不是 try catch,因为结果将在稍后时间交付,这样就不会阻塞当前调用队列。在完成块中,您要么有 value 或者 error

您通过 storage.async 访问异步API,它是线程安全的,您可以根据需要顺序使用同步和异步API。所有异步函数都受限于 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(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(forKey: "my favorite city") { result in
  if case .value(let exists) = result, exists {
    print("I have a favorite city")
  }
}

storage.async.removeAll() { result in
  switch result {
    case .value:
      print("removal completes")
    case .error(let error):
      print(error)
  }
}

storage.async.removeExpiredObjects() { result in
  switch result {
    case .value:
      print("removal completes")
    case .error(let error):
      print(error)
  }
}

过期时间

默认情况下,所有已保存的对象都与其在 DiskConfigMemoryConfig 中指定的过期时间相同。您可以通过为 setObject 指定 expiry 来覆盖特定对象的过期时间。

// Default expiry date from configuration will be applied to the item
try? storage.setObject("This is a string", forKey: "string")

// A given expiry date will be applied to the item
try? storage.setObject(
  "This is a string",
  forKey: "string"
  expiry: .date(Date().addingTimeInterval(2 * 3600))
)

// Clear expired objects
storage.removeExpiredObjects()

观察

存储 允许您观察缓存层的变化,无论是在存储层面还是在键层面。API 允许您传递任何对象作为观察者,同时还可以传递一个观察闭包。当弱捕获的观察者被回收时,观察闭包将自动移除。

存储观察

// Add observer
let token = storage.addStorageObserver(self) { observer, storage, change in
  switch change {
  case .add(let key):
    print("Added \(key)")
  case .remove(let key):
    print("Removed \(key)")
  case .removeAll:
    print("Removed all")
  case .removeExpired:
    print("Removed expired")
  }
}

// Remove observer
token.cancel()

// Remove all observers
storage.removeAllStorageObservers()

键观察

let key = "user1"

let token = storage.addObserver(self, forKey: key) { observer, storage, change in
  switch change {
  case .edit(let before, let after):
    print("Changed object for \(key) from \(String(describing: before)) to \(after)")
  case .remove:
    print("Removed \(key)")
  }
}

// Remove observer by token
token.cancel()

// Remove observer for key
storage.removeObserver(forKey: key)

// Remove all observers
storage.removeAllKeyObservers()

处理 JSON 响应

大多数情况下,我们的用例是从后端获取一些 JSON,显示它同时将 JSON 保存到存储中供将来使用。如果您使用类似 AlamofireMalibu 的库,您可以以字典、字符串或数据的形式获取 JSON。

存储 可以持久化 StringData。您甚至可以使用 JSONArrayWrapperJSONDictionaryWrapper 将 JSON 保存到 存储 中,但我们更喜欢持久化强类型对象,因为这些都是您用于显示 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)
  }
}

关于图片怎么办

如果您想将图片加载到 UIImageViewNSImageView 中,那么我们还为您准备了一个礼物。它被称为 Imaginary,并使用 Cache 来简化您处理远程图片时的工作。

安装

CocoaPods

缓存可通过CocoaPods获取。要安装,只需在Podfile中添加以下行

pod 'Cache'

Carthage

缓存也通过Carthage获取。要安装,只需在Cartfile中写入以下内容

github "hyperoslo/Cache"

您还需要在copy-frameworks脚本中添加SwiftHash.framework

作者

贡献

我们非常欢迎您为缓存做出贡献,请查看CONTRIBUTING文件获取更多信息。

许可协议

缓存可在MIT许可下使用。更多信息请参阅LICENSE文件。