我一直在关注这里找到的关于Caching in Swift的指南。
我目前收到错误Type 'Cache' does not conform to protocol 'Encodable'
这对我来说毫无意义,因为我遵循了这封信的指南,我发现有几个人在 GitHub 上使用相同的缓存,我相信我的输出与他们的匹配。
为什么“缓存”不符合?
我添加了下面教程中创建的完整类 -
final class Cache<Key: Hashable, Value> {
private let wrapped = NSCache<WrappedKey, Entry>()
private let entryLifetime: TimeInterval
private let dateProvider: () -> Date
private let keyTracker = KeyTracker()
init(dateProvider: @escaping () -> Date = Date.init,entryLifetime: TimeInterval = 12 * 60 * 60, maximumEntryCount: Int = 50) {
self.dateProvider = dateProvider
self.entryLifetime = entryLifetime
wrapped.countLimit = maximumEntryCount
wrapped.delegate = keyTracker
}
func insert(_ value: Value, forKey key: Key) {
let date = dateProvider().addingTimeInterval(entryLifetime)
let entry = Entry(key: key, value: value, expirationDate: date)
let wrappedKey = WrappedKey(key: key)
wrapped.setObject(entry, forKey: wrappedKey)
keyTracker.keys.insert(key)
}
func value(forKey key: Key) -> Value? {
guard let entry = wrapped.object(forKey: WrappedKey(key: key)) else { return nil }
guard dateProvider() < entry.expirationDate else {
//Discard expired values
removeValue(forKey: key)
return nil
}
return entry.value
}
func removeValue(forKey key: Key) {
let key = WrappedKey(key: key)
wrapped.removeObject(forKey: key)
}
}
private extension Cache {
final class WrappedKey: NSObject {
let key: Key
init(key: Key) {
self.key = key
}
override var hash: Int { return key.hashValue }
override func isEqual(_ object: Any?) -> Bool {
guard let value = object as? WrappedKey else { return false }
return value.key == key
}
}
}
private extension Cache {
final class Entry {
let key: Key
let value: Value
let expirationDate: Date
init(key: Key, value: Value, expirationDate: Date) {
self.key = key
self.value = value
self.expirationDate = expirationDate
}
}
}
extension Cache {
subscript(key: Key) -> Value? {
get { return value(forKey: key) }
set {
guard let value = newValue else {
//If nil is assigned using subscript then remove any value for that key
removeValue(forKey: key)
return
}
insert(value, forKey: key)
}
}
}
private extension Cache {
final class KeyTracker: NSObject, NSCacheDelegate {
var keys = Set<Key>()
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject object: Any) {
guard let entry = object as? Entry else { return }
keys.remove(entry.key)
}
}
}
extension Cache.Entry: Codable where Key: Codable, Value: Codable {}
private extension Cache {
func entry(forKey key: Key) -> Entry? {
guard let entry = wrapped.object(forKey: WrappedKey(key: key)) else { return nil }
guard dateProvider() < entry.expirationDate else {
removeValue(forKey: key)
return nil
}
return entry
}
func insert(_ entry: Entry) {
wrapped.setObject(entry, forKey: WrappedKey(key: entry.key))
keyTracker.keys.insert(entry.key)
}
}
extension Cache: Codable where Key: Codable, Value: Codable {
convenience init(from decoder: Decoder) throws {
self.init()
let container = try decoder.singleValueContainer()
let entries = try container.decode([Entry].self)
entries.forEach(insert)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(keyTracker.keys.compactMap(entry))
}
}
extension Cache where Key: Codable, Value: Codable {
func saveToDisk(withName name: String, using fileManager: FileManager = .default) throws {
let folderURLs = fileManager.urls(
for: .cachesDirectory,
in: .userDomainMask
)
let fileURL = folderURLs[0].appendingPathComponent(name + ".cache")
let data = try JSONEncoder().encode(self)
try data.write(to: fileURL)
}
}