Manchmal ist es vielleicht bequemer, nicht mit Core Data zu arbeiten und nur den Cache-Inhalt auf der Festplatte zu speichern. Sie können dies mit NSKeyedArchiver
und UserDefaults
erreichen (ich verwende Swift 3.0.2 in Codebeispielen unten).
Lassen Sie uns zunächst abstrakt von NSCache
und sich vorstellen, dass wir jede Cache können bestehen bleiben wollen, das Protokoll entspricht:
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
zugehörigen Typ hat sein Hashable
denn das ist Voraussetzung für Set
Typparameter.
Als nächstes haben wir NSCoding
für Cache
mit Hilfsklasse CacheCoding
implementieren:
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)
}
}
hier:
C
ist der Typ, der Cache
entspricht.
C.Key
zugehöriger Typ muss entsprechen:
- Swift
CustomStringConvertible
Protokoll sein umwandelbar String
weil NSCoder.encode(forKey:)
Methode String
für Schlüsselparameter übernimmt.
- Swift
ExpressibleByStringLiteral
Protokoll [String]
zurück Set<Key>
- Wir müssen konvertieren
Set<Key>
-[String]
umwandeln und speichern mit keys
Schlüssel zu NSCoder
weil es keine Möglichkeit während der Decodierung von NSCoder
Schlüssel zu extrahieren, die verwendet wurden bei der Encodierung Objekte. Aber es kann Situation geben, wenn wir auch einen Eintrag im Cache mit dem Schlüssel keys
haben, um die Cacheschlüssel vom speziellen Schlüssel keys
zu unterscheiden, setzen wir die Cacheschlüssel mit _
voran.
C.Value
zugehöriger Typ muss NSCodingConvertible
Protokoll entsprechen NSCoding
Instanzen von den Werten im Cache gespeichert zu bekommen:
protocol NSCodingConvertible {
associatedtype Coding: NSCoding
var coding: Coding { get }
}
Value.Coding
muss ValueProvider
Protokoll entsprechen, weil Sie Werte wieder von NSCoding
Instanzen erhalten müssen:
protocol ValueProvider {
associatedtype Value
var value: Value { get }
}
C.Value.Coding.Value
und C.Value
haben gleichwertig ist becau sse den Wert, von dem wir NSCoding
Instanz erhalten, wenn Codierung muss den gleichen Typ wie Wert haben, die wir von NSCoding
bei der Decodierung erhalten.
CB
ist ein Typ, der zu Builder
Protokoll entspricht und hilft Cache-Instanz von C
Art zu schaffen:
protocol Builder {
associatedtype Value
init()
func build() -> Value
}
Als nächstes wollen wir NSCache
zu Cache
Protokoll konform machen. Hier haben wir ein Problem. NSCache
hat das gleiche Problem wie NSCoder
tut - es bietet nicht die Möglichkeit, Schlüssel für gespeicherte Objekte zu extrahieren. Es gibt drei Möglichkeiten, dies zu umgehen:
Wrap NSCache
mit benutzerdefinierten Typ, die Set
Schlüssel halten und es überall verwenden statt 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)
}
}
Wenn Sie noch NSCache
müssen irgendwo dann passieren Sie können versuchen, es in Objective-C zu erweitern, indem Sie dasselbe tun wie oben mit BetterCache
.
Verwenden Sie eine andere Cache-Implementierung.
Jetzt haben Sie Typ, der Cache
Protokoll entspricht und Sie sind bereit, es zu benutzen.
des definieren Lassen Typ Book
die Instanzen wir in Cache und NSCoding
für diese Art gespeichert werden:
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)
}
}
Einige typealiases zur besseren Lesbarkeit:
typealias BookCache = BetterCache<StringKey, Book>
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder>
und Baumeister, die uns Cache
instanziiert helfen Beispiel:
class BookCacheBuilder: Builder {
required init() {
}
func build() -> BookCache {
return BookCache()
}
}
Test-it:
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)
}
}
Ich habe Core-Daten für fast jede Datenbank-App verwendet, die ich erstellt habe, aber es scheint nur, dass dies nicht die beste Übereinstimmung dafür ist. Ich benutze die Caches, um API-Ergebnisse zu speichern, und halte die App beim Start und beim Laden von bereits geladenen Dingen zippy. Ich mache mir Sorgen, dass die Kerndaten-Datenbank aus dem Ruder läuft und wächst. Es scheint einfach nicht, dass Kerndaten am besten für das Zwischenspeichern "temporärer" Daten verwendet werden. Die Persistenz, die ich brauche, ist im Grunde eine sekundäre Funktion der In-Memory-Caching-Funktionalität, die ich brauche, weshalb ich mich zu NSCache hingezogen habe. –
Yah - Ich kann dein Rätsel sehen. NSCoding ist nicht * das * schwer zu implementieren - Sie könnten immer diesen Weg gehen. Dann stellt sich die Frage, wann die persistente Version des Caches geschrieben/aktualisiert werden soll. Meiner Erfahrung nach ist es ziemlich einfach, einen Weg einzuschlagen, der das Persistenzrad mit all seinen Komplexitäten neu erfindet. Und natürlich ist die beste Anwendung diejenige, die zuerst geliefert wird. ;) – bbum
@CoryImdieke, wir stehen jetzt vor der gleichen Situation und ich plane, NSCache zu verwenden. Sie haben sich nur gefragt, welche Lösung Sie gewählt haben? – Koolala