18

たくさんのオブジェクトのメモリ内キャッシュを保持する必要があるアプリを書いていますが、それは手に負えないので、NSCacheを使用してすべてを保存することを計画しています。私にとっては、パージなどの面倒を見ることができるようです。これは素晴らしいことです。

また、起動間でキャッシュを保持したいので、キャッシュデータをディスクに書き込む必要があります。NSCacheの内容をplistなどに保存する簡単な方法はありますか?NSCache以外のものを使用してこれを達成するためのより良い方法はおそらくありますか?

このアプリはiPhoneに搭載されるので、OS Xだけでなく、iOS4以降で利用できるクラスのみが必要になります。

ありがとう!

4

4 に答える 4

13

たくさんのオブジェクトのメモリ内キャッシュを保持する必要があるアプリを書いていますが、それは手に負えないので、NSCacheを使用してすべてを保存することを計画しています。私にとっては、パージなどの面倒を見ることができるようです。これは素晴らしいことです。また、起動間でキャッシュを保持したいので、キャッシュデータをディスクに書き込む必要があります。NSCacheの内容をplistなどに保存する簡単な方法はありますか?NSCache以外のものを使用してこれを達成するためのより良い方法はおそらくありますか?

CoreDataが何をするのかを正確に説明しました。パージおよびプルーニング機能を備えたオブジェクトグラフの永続性。

NSCache永続性を支援するようには設計されていません。

plist形式に固執することを提案したことを考えると、代わりにCoreDataを使用することは概念的な違いほど大きくはありません。

于 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これはとで実現できます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ために実装する必要があります。CacheCacheCoding

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関連するタイプは、以下に準拠する必要があります。
    • メソッドがキーパラメータを受け入れるため、変換可能なSwiftCustomStringConvertibleプロトコル。StringNSCoder.encode(forKey:)String
    • に戻すためのSwiftExpressibleByStringLiteralプロトコル[String]Set<Key>
  • オブジェクトのエンコード時に使用されたキーからデコード中に抽出する方法がないため、キーを使用して変換Set<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あるため、同等である必要があります。NSCodingNSCoding

  • CBプロトコルに準拠し、次のタイプBuilderのキャッシュインスタンスを作成するのに役立つCタイプです。

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

次に、プロトコルにNSCache準拠させましょう。Cacheここに問題があります。NSCacheと同じ問題がNSCoderあります-保存されたオブジェクトのキーを抽出する方法を提供しません。これを回避するには、次の3つの方法があります。

  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 に答える