我正在编写一个应用程序,需要在内存中保留一堆对象的缓存,但是为了避免混乱,我计划使用NSCache来存储所有内容。看起来它会负责清理等工作,这太好了。
我还想将缓存持久化到磁盘上,以便在启动时恢复缓存数据。有没有一种简单的方法将NSCache的内容保存为plist或其他格式?也许有更好的方法可以使用不同于NSCache的东西来完成这个任务吗?
这个应用程序将在iPhone上运行,所以我只需要使用iOS 4+可用的类库,而不是仅限于OS X。
谢谢!
我正在编写一个应用程序,需要在内存中保留一堆对象的缓存,但是为了避免混乱,我计划使用NSCache来存储所有内容。看起来它会负责清理等工作,这太好了。
我还想将缓存持久化到磁盘上,以便在启动时恢复缓存数据。有没有一种简单的方法将NSCache的内容保存为plist或其他格式?也许有更好的方法可以使用不同于NSCache的东西来完成这个任务吗?
这个应用程序将在iPhone上运行,所以我只需要使用iOS 4+可用的类库,而不是仅限于OS X。
谢谢!
使用TMCache (https://github.com/tumblr/TMCache)。它类似于NSCache,但具有持久性和缓存清除功能。由Tumblr团队编写。
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
类型参数的要求。
接下来,我们需要使用辅助类CacheCoding
实现Cache
的NSCoding
。
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
is type that conforms to Cache
.C.Key
associated type has to conform to:
CustomStringConvertible
protocol to be convertible to String
because NSCoder.encode(forKey:)
method accepts String
for key parameter. ExpressibleByStringLiteral
protocol to convert [String]
back to Set<Key>
Set<Key>
to [String]
and store it to NSCoder
with keys
key because there is no way to extract during decoding from NSCoder
keys that were used when encoding objects. But there may be situation when we also have entry in cache with key keys
so to distinguish cache keys from special keys
key we prefix cache keys with _
.C.Value
associated type has to conform to NSCodingConvertible
protocol to get NSCoding
instances from the values stored in cache:
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
has to conform to ValueProvider
protocol because you need to get values back from NSCoding
instances:
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
and C.Value
have to be equivalent because the value from which we get NSCoding
instance when encoding must have the same type as value that we get back from NSCoding
when decoding.
CB
is a type that conforms to Builder
protocol and helps to create cache instance of C
type:
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
NSCache
符合Cache
协议。这里我们有一个问题。NSCache
和NSCoder
一样存在一个问题 - 它不提供提取存储对象键的方法。有三种解决方法:
Wrap NSCache
with custom type which will hold keys Set
and use it everywhere instead of 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)
}
}
If you still need to pass NSCache
somewhere then you can try to extend it in Objective-C doing the same thing as I did above with BetterCache
.
Use some other cache implementation.
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)
}
}
你应该尝试使用AwesomeCache。它的主要特点包括:
示例:
do {
let cache = try Cache<NSString>(name: "awesomeCache")
cache["name"] = "Alex"
let name = cache["name"]
cache["name"] = nil
} catch _ {
print("Something went wrong :(")
}