将NSCache内容保存到磁盘

时间:2010-12-27 23:47:34

标签: iphone cocoa-touch cocoa nscache

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

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

此应用程序将在iPhone上,因此我只需要iOS 4+以上的类,而不仅仅是OS X.

谢谢!

4 个答案:

答案 0 :(得分:13)

  

我正在编写一个需要保留的应用   一堆内存缓存   对象,但不会离开   所以我打算使用NSCache   存储所有。看起来会   照顾清洗等对我来说,   太棒了。   我也想保留缓存   在发布之间,所以我需要写   缓存数据到磁盘。有没有   保存NSCache内容的简便方法   到一个plist或什么?在那儿   也许更好的方法来实现这一目标   使用NSCache以外的东西?

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

NSCache并非旨在协助持久性。

鉴于您建议坚持使用plist格式,因此使用Core Data并不是一个很大的概念差异。

答案 1 :(得分:9)

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

答案 2 :(得分:4)

有时,不处理Core Data并将缓存内容保存到磁盘可能更方便。您可以使用NSKeyedArchiverUserDefaults完成此操作(我在下面的代码示例中使用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类型参数。

接下来,我们必须使用帮助程序类NSCodingCache实现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关联类型必须符合:
    • Swift CustomStringConvertible协议可转换为String,因为NSCoder.encode(forKey:)方法接受String作为关键参数。
    • Swift ExpressibleByStringLiteral协议将[String]转换回Set<Key>
  • 我们需要将Set<Key>转换为[String]并使用NSCoder密钥将其存储到keys,因为在NSCoder密钥解码过程中无法提取编码对象时使用的。但是,当我们在密钥keys的缓存中进入时,可能存在这样的情况,因此为了区分缓存密钥和特殊keys密钥,我们使用_为缓存密钥添加前缀。
  • 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.ValueC.Value必须相同,因为我们在编码时获得NSCoding实例的值必须与我们从{{{{}获取的值具有相同的类型1}}解码时。

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

    C

接下来让我们protocol Builder { associatedtype Value init() func build() -> Value } 符合NSCache协议。我们有一个问题。 CacheNSCache具有相同的问题 - 它没有提供为存储对象提取密钥的方法。有三种方法可以解决这个问题:

  1. 使用自定义类型换取NSCoder,其中包含密钥NSCache并在任何地方使用它而不是Set

    NSCache
  2. 如果你仍然需要在某个地方传递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) } } ,那么你可以尝试在Objective-C中扩展它,就像我上面用NSCache做的那样。

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

  4. 现在您的类型符合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)
        }
    }
    

    将帮助我们实例化typealias BookCache = BetterCache<StringKey, Book> typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder> 实例的构建器:

    Cache

    测试它:

    class BookCacheBuilder: Builder {
        required init() {
        }
    
        func build() -> BookCache {
            return BookCache()
        }
    }
    

答案 3 :(得分:0)

您应该尝试AwesomeCache。其主要特点:

  • 用Swift编写
  • 用于磁盘缓存
  • 由NSCache支持,以获得最佳性能并支持单个对象的到期

示例:

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

    cache["name"] = "Alex"
    let name = cache["name"]
    cache["name"] = nil
} catch _ {
    print("Something went wrong :(")
}