Cor*_*eke 15 iphone cocoa cocoa-touch nscache
我正在编写一个应用程序,需要保留一堆对象的内存缓存,但这不会失控,所以我打算使用NSCache来存储它.看起来它会照顾清除等对我而言,这太棒了.
我还想在启动之间保持缓存,所以我需要将缓存数据写入磁盘.有没有一种简单的方法可以将NSCache内容保存到plist或其他东西?是否有更好的方法可以使用NSCache之外的其他东西来实现这一目标?
这个应用程序将在iPhone上,所以我只需要iOS 4+中提供的类而不仅仅是OS X.
谢谢!
bbu*_*bum 13
我正在编写一个应用程序,需要保留一堆对象的内存缓存,但这不会失控,所以我打算使用NSCache来存储它.看起来它会照顾清除等对我而言,这太棒了.我还想在启动之间保持缓存,所以我需要将缓存数据写入磁盘.有没有一种简单的方法可以将NSCache内容保存到plist或其他东西?是否有更好的方法可以使用NSCache之外的其他东西来实现这一目标?
你几乎只是描述了CoreData的功能; 具有清除和修剪功能的对象图的持久性.
NSCache 不是为了协助持久性而设计的.
鉴于您建议坚持使用plist格式,使用Core Data代替并不是那么大的概念差异.
有时,不处理核心数据而仅将缓存内容保存到磁盘可能更方便。NSKeyedArchiver您可以使用and来实现此目的UserDefaults(我在下面的代码示例中使用 Swift 3.0.2)。
首先让我们抽象NSCache并想象我们希望能够持久化任何符合协议的缓存:
protocol Cache {
associatedtype Key: Hashable
associatedtype Value
var keys: Set<Key> { get }
func set(value: Value, forKey key: Key)
func value(forKey key: Key) -> Value?
func removeValue(forKey key: Key)
}
extension Cache {
subscript(index: Key) -> Value? {
get {
return value(forKey: index)
}
set {
if let v = newValue {
set(value: v, forKey: index)
} else {
removeValue(forKey: index)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Key关联类型必须是,Hashable因为这是类型参数的要求Set。
接下来我们必须实现NSCoding使用Cache辅助类CacheCoding:
private let keysKey = "keys"
private let keyPrefix = "_"
class CacheCoding<C: Cache, CB: Builder>: NSObject, NSCoding
where
C.Key: CustomStringConvertible & ExpressibleByStringLiteral,
C.Key.StringLiteralType == String,
C.Value: NSCodingConvertible,
C.Value.Coding: ValueProvider,
C.Value.Coding.Value == C.Value,
CB.Value == C {
let cache: C
init(cache: C) {
self.cache = cache
}
required convenience init?(coder decoder: NSCoder) {
if let keys = decoder.decodeObject(forKey: keysKey) as? [String] {
var cache = CB().build()
for key in keys {
if let coding = decoder.decodeObject(forKey: keyPrefix + (key as String)) as? C.Value.Coding {
cache[C.Key(stringLiteral: key)] = coding.value
}
}
self.init(cache: cache)
} else {
return nil
}
}
func encode(with coder: NSCoder) {
for key in cache.keys {
if let value = cache[key] {
coder.encode(value.coding, forKey: keyPrefix + String(describing: key))
}
}
coder.encode(cache.keys.map({ String(describing: $0) }), forKey: keysKey)
}
}
Run Code Online (Sandbox Code Playgroud)
这里:
C是符合 的类型Cache。C.Key关联类型必须符合:
CustomStringConvertible可转换为Swift协议String,因为NSCoder.encode(forKey:)方法接受String关键参数。ExpressibleByStringLiteral转换[String]回Swift协议Set<Key>Set<Key>并[String]存储为NSCoderwith keyskey,因为在解码过程中无法从NSCoder编码对象时使用的密钥中提取。但是可能存在这样的情况,当我们在缓存中也有带有键的条目时keys,为了区分缓存键和特殊keys键,我们在缓存键前面加上_。C.Value关联类型必须符合协议才能从缓存中存储的值NSCodingConvertible获取实例:NSCoding
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Run Code Online (Sandbox Code Playgroud)Value.Coding必须符合协议,因为您需要从实例ValueProvider中获取值:NSCoding
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
Run Code Online (Sandbox Code Playgroud)C.Value.Coding.Value并且C.Value必须是等价的,因为编码时获取NSCoding实例的值必须与解码时获取的值具有相同的类型NSCoding。
CB是一种符合Builder协议的类型,有助于创建C类型的缓存实例:
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
Run Code Online (Sandbox Code Playgroud)接下来让我们NSCache遵守Cache协议。这里我们有一个问题。NSCache具有相同的问题NSCoder- 它不提供提取存储对象的密钥的方法。有三种方法可以解决这个问题:
使用自定义类型进行包装NSCache,该类型将保存键Set并在任何地方使用它,而不是NSCache:
class BetterCache<K: AnyObject & Hashable, V: AnyObject>: Cache {
private let nsCache = NSCache<K, V>()
private(set) var keys = Set<K>()
func set(value: V, forKey key: K) {
keys.insert(key)
nsCache.setObject(value, forKey: key)
}
func value(forKey key: K) -> V? {
let value = nsCache.object(forKey: key)
if value == nil {
keys.remove(key)
}
return value
}
func removeValue(forKey key: K) {
return nsCache.removeObject(forKey: key)
}
}
Run Code Online (Sandbox Code Playgroud)如果您仍然需要传递NSCache某个地方,那么您可以尝试在 Objective-C 中扩展它,执行与我上面使用BetterCache.
使用其他一些缓存实现。
现在您已经有了符合Cache协议的类型并且可以使用它了。
让我们定义Book我们将在缓存中存储哪些实例以及NSCoding该类型的类型:
class Book {
let title: String
init(title: String) {
self.title = title
}
}
class BookCoding: NSObject, NSCoding, ValueProvider {
let value: Book
required init(value: Book) {
self.value = value
}
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else {
return nil
}
print("My Favorite Book")
self.init(value: Book(title: title))
}
func encode(with coder: NSCoder) {
coder.encode(value.title, forKey: "title")
}
}
extension Book: NSCodingConvertible {
var coding: BookCoding {
return BookCoding(value: self)
}
}
Run Code Online (Sandbox Code Playgroud)
为了更好的可读性,一些类型别名:
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
Run Code Online (Sandbox Code Playgroud)
构建器将帮助我们实例化Cache实例:
class BookCacheBuilder: Builder {
required init() {
}
func build() -> BookCache {
return BookCache()
}
}
Run Code Online (Sandbox Code Playgroud)
测试一下:
let cacheKey = "Cache"
let bookKey: StringKey = "My Favorite Book"
func test() {
var cache = BookCache()
cache[bookKey] = Book(title: "Lord of the Rings")
let userDefaults = UserDefaults()
let data = NSKeyedArchiver.archivedData(withRootObject: BookCacheCoding(cache: cache))
userDefaults.set(data, forKey: cacheKey)
userDefaults.synchronize()
if let data = userDefaults.data(forKey: cacheKey),
let cache = (NSKeyedUnarchiver.unarchiveObject(with: data) as? BookCacheCoding)?.cache,
let book = cache.value(forKey: bookKey) {
print(book.title)
}
}
Run Code Online (Sandbox Code Playgroud)