2010-12-27 8 views
12

Ich schreibe eine App, die einen speicherinternen Cache mit einer Menge von Objekten speichern muss, aber das kommt nicht aus dem Ruder, deshalb plane ich, NSCache zu verwenden, um alles zu speichern . Sieht so aus als ob es für mich sauber machen würde und das ist fantastisch.Inhalt von NSCache auf Diskette speichern

Ich möchte auch den Cache zwischen den Starts beibehalten, also muss ich die Cache-Daten auf die Festplatte schreiben. Gibt es eine einfache Möglichkeit, den NSCache-Inhalt auf einem Plist oder etwas zu speichern? Gibt es vielleicht bessere Möglichkeiten, dies mit etwas anderem als NSCache zu erreichen?

Diese App wird auf dem iPhone sein, also werde ich nur Klassen müssen, die in iOS verfügbar sind 4+ und nicht nur X. OS

Dank!

Antwort

13

Ich schreibe eine Anwendung, die einen In-Memory-Cache eines Bündels von Objekte halten muss, aber das ist nicht aus Hand bekommt so weiter ich plane nscache mit speichern es alles. Sieht aus wie es wird kümmern sich um die Reinigung und so für mich, das ist fantastisch. Ich würde auch gerne den Cache zwischen den Einführungen beibehalten, also muss ich die Cache-Daten auf die Festplatte schreiben. Gibt es eine einfache Möglichkeit, den NSCache Inhalt zu einem Plist oder etwas zu speichern? Gibt es vielleicht bessere Möglichkeiten, dies zu erreichen mit etwas anderem als NSCache?

Sie haben ziemlich genau genau beschrieben, was CoreData tut; Persistenz von Objektdiagrammen mit Bereinigungs- und Beschneidungsfunktionen.

NSCache wurde nicht entwickelt, um Persistenz zu unterstützen.

Da Sie vorgeschlagen haben, in einem plist-Format zu bleiben, ist die Verwendung von Core Data kein großer konzeptioneller Unterschied.

+0

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. –

+0

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

+0

@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

9

Verwenden Sie TMCache (https://github.com/tumblr/TMCache). Es ist wie NSCache, aber mit Persistenz und Cache-Bereinigung. Geschrieben vom Tumblr-Team.

+1

Leider wird es nicht mehr aktiv gepflegt:/ – manicaesar

+0

Beste Antwort jetzt ist entweder CoreData oder Realm. Coredata ist am meisten Standard, Realm einfacher zu lernen. –

0

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:

  1. 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) 
        } 
    } 
    
  2. 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.

  3. 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) 
    } 
} 
0

Sie sollten AwesomeCache versuchen. Die wichtigsten Merkmale:

  • in Swift geschrieben
  • verwenden auf On-Disk-Caching
  • von nscache gesichert für maximale Leistung und Unterstützung für Ablauf der einzelnen Objekte

Beispiel:

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

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