18

我正在编写一个需要保留一堆对象的内存缓存的应用程序,但这并没有失控,所以我打算使用 NSCache 来存储它。看起来它会照顾我的清洗等,这太棒了。

我还想在启动之间保留缓存,所以我需要将缓存数据写入磁盘。有没有一种简单的方法可以将 NSCache 内容保存到 plist 或其他东西?是否有更好的方法可以使用 NSCache 以外的东西来实现这一点?

这个应用程序将在 iPhone 上,所以我只需要 iOS 4+ 中可用的类,而不仅仅是 OS X。

谢谢!

4

4 回答 4

13

我正在编写一个需要保留一堆对象的内存缓存的应用程序,但这并没有失控,所以我打算使用 NSCache 来存储它。看起来它会照顾我的清洗等,这太棒了。我还想在启动之间保留缓存,所以我需要将缓存数据写入磁盘。有没有一种简单的方法可以将 NSCache 内容保存到 plist 或其他东西?是否有更好的方法可以使用 NSCache 以外的东西来实现这一点?

您几乎只是准确地描述了 CoreData 的作用;具有清除和修剪功能的对象图的持久性。

NSCache并非旨在帮助持久性。

鉴于您建议坚持使用 plist 格式,因此使用 Core Data 并没有太大的概念差异。

于 2010-12-28T09:08:16.017 回答
9

使用 TMCache ( https://github.com/tumblr/TMCache )。它类似于 NSCache,但具有持久性和缓存清除功能。由 Tumblr 团队撰写。

于 2013-11-07T02:47:17.657 回答
4

有时不处理 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)
            }
        }
    }
}

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)
    }
}

这里:

  • C是符合 的类型Cache
  • C.Key关联类型必须符合:
    • CustomStringConvertible可转换为Swift协议,String因为NSCoder.encode(forKey:)方法接受Stringkey 参数。
    • ExpressibleByStringLiteral要转换[String]回的Swift协议Set<Key>
  • 我们需要使用keySet<Key>转换[String]并存储它,因为在解码期间无法从编码对象时使用的键中提取。但是可能会有这样的情况,我们在缓存中也有带有键的条目,因此为了区分缓存键和特殊键,我们在缓存键前面加上.NSCoderkeysNSCoderkeyskeys_
  • C.Value关联类型必须符合NSCodingConvertible协议才能从缓存中存储的值中获取NSCoding实例:

    protocol NSCodingConvertible {
        associatedtype Coding: NSCoding
    
        var coding: Coding { get }
    }
    
  • Value.Coding必须符合协议,因为您需要从实例ValueProvider中取回值:NSCoding

    protocol ValueProvider {
        associatedtype Value
    
        var value: Value { get }
    }
    
  • C.Value.Coding.Value并且C.Value必须是等价的,因为我们NSCoding在编码时获得实例的值必须与我们在解码时获得的值具有相同的类型NSCoding

  • CB是一种符合Builder协议并有助于创建缓存实例的C类型:

    protocol Builder {
        associatedtype Value
    
        init()
    
        func build() -> Value
    }
    

接下来让我们NSCache符合Cache协议。这里我们有一个问题。NSCache有同样的问题NSCoder- 它不提供为存储对象提取密钥的方法。有三种方法可以解决此问题:

  1. 使用自定义类型进行包装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)
        }
    }
    
  2. 如果你仍然需要通过NSCache某个地方,那么你可以尝试在 Objective-C 中扩展它,就像我在上面用BetterCache.

  3. 使用其他一些缓存实现。

现在你有了符合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)
    }
}

一些类型别名以获得更好的可读性:

typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>

以及将帮助我们实例化Cache实例的构建器:

class BookCacheBuilder: Builder {
    required init() {
    }

    func build() -> BookCache {
        return BookCache()
    }
}

测试它:

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)
    }
}
于 2017-03-26T09:04:51.090 回答
0

你应该试试AwesomeCache。其主要特点:

  • 用斯威夫特写的
  • 用于磁盘缓存
  • 由 NSCache 支持以获得最佳性能并支持单个对象的到期

例子:

do {
    let cache = try Cache<NSString>(name: "awesomeCache")

    cache["name"] = "Alex"
    let name = cache["name"]
    cache["name"] = nil
} catch _ {
    print("Something went wrong :(")
}
于 2017-03-26T09:07:59.560 回答