コアデータを処理せず、単にキャッシュの内容をディスクに保存しない方が便利な場合があります。 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
関連するタイプは、それがSet
タイプパラメータの要件だからHashable
なければなりません。ここで
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)
}
}
:
次はヘルパークラスCacheCoding
を使用してCache
ためNSCoding
を実装する必要が
C
はCache
に準拠タイプです。NSCoder.encode(forKey:)
方法は、キーパラメータのString
を受け入れるためString
に変換すること
- スウィフト
CustomStringConvertible
プロトコル:
C.Key
関連タイプに適合しなければなりません。バック符号化するときに使用されたNSCoder
キーから復号化中に抽出する方法がないので、我々は[String]
にSet<Key>
を変換しkeys
キーでNSCoder
にそれを格納する必要Set<Key>
- から
[String]
を変換する
- スウィフト
ExpressibleByStringLiteral
プロトコルオブジェクト。しかし、キャッシュにkeys
というエントリがあると、キャッシュキーをkeys
キーと区別するために、プレフィックスキャッシュキーに_
を付けることもあります。
C.Value
関連するタイプは、キャッシュに格納された値からNSCoding
のインスタンスを取得するためにNSCodingConvertible
プロトコルに準拠する必要があります:あなたはNSCoding
インスタンスから値を取り戻す必要があるため
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
はValueProvider
プロトコルに準拠しています
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
とC.Value
は同等でなければならないbecau私たちがNSCoding
のインスタンスを取得する際の値は、デコード時にNSCoding
から返される値と同じタイプのエンコードでなければなりません。
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
次に者がNSCache
がCache
プロトコルに準拠してみましょう:
CB
はBuilder
プロトコルに準拠し、C
タイプのキャッシュ・インスタンスを作成するのに役立ちますタイプです。ここには問題があります。 NSCache
にはNSCoder
と同じ問題があります。格納されたオブジェクトのキーを抽出する方法はありません。この問題を回避するには、3つの方法があります。
キー
Set
を保持し、
NSCache
するのではなく、どこでもそれを使用します
カスタムタイプとラップ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)
}
}
をあなたはまだ、その後どこかNSCache
を渡す必要がある場合あなたはObjective-Cの中で、上記と同じことをBetterCache
として拡張しようとすることができます。
他のキャッシュ実装を使用してください。
これでCache
プロトコルに準拠したタイプになっています。使用する準備が整いました。
のは、我々はそのタイプのキャッシュとNSCoding
に格納されますどのインスタンスタイプBook
を定義してみましょう:
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)
}
}
読みやすくするためのいくつかのtypealiases:
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)
}
}
作成したほぼすべてのデータベースアプリケーションでCore Dataを使用しましたが、これはこれに最適な方法ではないようです。私はAPIの結果を保存するためにキャッシュを使用しています。起動時に、また既に読み込まれたものをロードするときには、アプリを賢明に保ちます。コアデータのデータベースが手に入らずに成長していくことを心配しています。コアデータが「一時的な」データのキャッシュに最もよく使用されるようには見えません。私が必要とする永続性は、基本的に私が必要とするメモリ内キャッシング機能の第2の機能です。そのため、NSCacheに傾いていました。 –
Yah - 私はあなたの謎を見ることができます。 NSCodingは実装が難しいことではありません。いつでもそのパスを辿ることができます。次に、永続的なバージョンのキャッシュを書き込み/更新するときに問題が発生します。私の経験では、すべての複雑さを伴う永続性ホイールの再発明を終える道を辿るのはかなり簡単です。もちろん、最も優れたアプリケーションは最初に出荷されるアプリケーションです。 ;) – bbum
@CoryImdieke、私たちは現在同じ状況に直面しており、NSCacheを使用する予定です。ちょうどあなたが選択した解決方法が不思議ですか? – Koolala