2010-12-27 6 views
12

J'écris une application qui doit conserver un cache en mémoire d'un tas d'objets, mais cela ne dégénère pas, donc je prévois d'utiliser NSCache pour tout stocker . On dirait que ça prendra soin de purger et tel pour moi, ce qui est fantastique. Je voudrais également maintenir le cache entre les lancements, donc j'ai besoin d'écrire les données de cache sur le disque. Existe-t-il un moyen facile de sauvegarder le contenu de NSCache sur une plist ou quelque chose? Y a-t-il peut-être de meilleurs moyens d'accomplir cela en utilisant autre chose que NSCache?Enregistrer le contenu NSCache sur le disque

Cette application sera sur l'iPhone, donc je vais avoir besoin uniquement des classes qui sont disponibles dans iOS 4+ et non seulement OS X.

Merci!

Répondre

13

J'écris une application qui a besoin de garder un cache d'un tas d'objets en mémoire, mais qui ne reçoit pas de main donc je prévois sur l'utilisation nscache pour stocker tout. On dirait que ça va prendre soin de purger et tel pour moi, ce qui est fantastique. Je voudrais également maintenir le cache entre les lancements, donc je dois écrire les données de cache sur le disque. Y at-il un moyen facile d'enregistrer le contenu NSCache à une plist ou quelque chose? Y at-il peut-être de meilleurs moyens d'accomplir cela en utilisant autre chose que NSCache?

Vous venez juste de décrire exactement ce que fait CoreData; persistance des graphes d'objets avec des capacités de purge et d'élagage.

NSCache n'est pas conçu pour aider à la persistance. Etant donné que vous avez suggéré de persister dans un format plist, l'utilisation de Core Data n'est pas une différence conceptuelle importante.

+0

J'ai utilisé des données de base sur presque toutes les applications de base de données que j'ai créées, mais il semble que ce ne soit pas la meilleure solution. J'utilise les caches pour stocker les résultats de l'API, en gardant l'application zippy au lancement et en chargeant les choses qui ont déjà été chargées. Je m'inquiète de voir la base de données de base devenir incontrôlable et grandissante. Il ne semble pas que les données de base soient mieux utilisées pour mettre en cache des données "temporaires". La persistance dont j'ai besoin est fondamentalement une fonction secondaire à la fonctionnalité de mise en cache en mémoire dont j'ai besoin, c'est pourquoi je me suis orienté vers NSCache. –

+0

Yah - Je peux voir votre énigme. NSCoding n'est pas * difficile * à mettre en œuvre - vous pouvez toujours suivre cette voie. Ensuite, la question devient quand écrire/mettre à jour la version persistante du cache. D'après mon expérience, il est assez facile de suivre une voie qui finit par réinventer la roue de la persistance avec toutes ses complexités. Et, bien sûr, l'application la plus performante est celle qui expédie en premier. ;) – bbum

+0

@CoryImdieke, nous sommes confrontés à la même situation maintenant et je prévois d'utiliser NSCache. Je me demandais quelle solution avez-vous choisie? – Koolala

9

utilisez TMCache (https://github.com/tumblr/TMCache). C'est comme NSCache mais avec persistance et purge du cache. Écrit par l'équipe Tumblr.

+1

Malheureusement, il n'est plus maintenu activement:/ – manicaesar

+0

meilleure réponse est maintenant soit CoreData ou Realm. Coredata est la norme, royaume plus facile à apprendre. –

0

Parfois, il peut être plus pratique de ne pas gérer les données de base et simplement de sauvegarder le contenu du cache sur le disque. Vous pouvez réaliser cela avec NSKeyedArchiver et UserDefaults (j'utilise Swift 3.0.2 dans les exemples de code ci-dessous).

D'abord nous allons faire abstraction de NSCache et imaginer que nous voulons être en mesure de persister tout cache conforme au protocole:

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 type associé doit être Hashable parce que c'est nécessaire pour Set paramètre de type.

Ensuite, nous devons mettre en œuvre NSCoding pour Cache en utilisant la classe d'aide CacheCoding:

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) 
    } 
} 

ici:

  • C est le type qui est conforme à Cache.
  • C.Key type associé doit être conforme à:
    • Swift CustomStringConvertible protocole à être converti en String parce que la méthode NSCoder.encode(forKey:) accepte String pour le paramètre clé.
    • Swift ExpressibleByStringLiteral protocole pour convertir [String] Retour à Set<Key>
  • Nous devons convertir Set<Key>-[String] et stocker à NSCoder avec keys clé, car il n'y a pas moyen d'extraire lors du décodage de NSCoder clés qui ont été utilisées lors de l'encodage objets. Mais il peut y avoir une situation où nous avons également l'entrée dans le cache avec la clé keys afin de distinguer les clés de cache de clé spéciale keys nous préfixons les clés de cache avec _.
  • C.Value type associé doit se conformer à NSCodingConvertible protocole pour obtenir NSCoding instances des valeurs stockées dans le cache:

    protocol NSCodingConvertible { 
        associatedtype Coding: NSCoding 
    
        var coding: Coding { get } 
    } 
    
  • Value.Coding doit se conformer à ValueProvider protocole car vous avez besoin pour obtenir des valeurs de retour de NSCoding instances:

    protocol ValueProvider { 
        associatedtype Value 
    
        var value: Value { get } 
    } 
    
  • C.Value.Coding.Value et C.Value doivent être équivalentes becau La valeur à partir de laquelle nous obtenons NSCoding instance lorsque l'encodage doit avoir le même type que la valeur que nous revenons de NSCoding lors du décodage.

  • CB est un type qui est conforme à Builder protocole et aide à créer une instance de cache de type C:

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

Ensuite, nous allons faire NSCache sont conformes à Cache protocole. Ici nous avons un problème. NSCache a le même problème que NSCoder - il ne fournit pas le moyen d'extraire les clés pour les objets stockés. Il y a trois façons de contourner cela:

  1. Wrap NSCache avec le type personnalisé qui conserve les clés Set et l'utiliser partout au lieu de 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. Si vous avez encore besoin de passer NSCache quelque part alors vous pouvez essayer de l'étendre en Objective-C en faisant la même chose que ci-dessus avec BetterCache.

  3. Utilisez une autre implémentation de cache.

Maintenant vous avez le type qui est conforme au protocole Cache et vous êtes prêt à l'utiliser.

Définissons le type Book qui cas, nous allons stocker dans le cache et NSCoding pour ce type:

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) 
    } 
} 

Quelques typealiases pour une meilleure lisibilité:

typealias BookCache = BetterCache<StringKey, Book> 
typealias BookCacheCoding = CacheCoding<BookCache, BookCacheBuilder> 

et le constructeur qui nous aidera à instancier Cache instance:

class BookCacheBuilder: Builder { 
    required init() { 
    } 

    func build() -> BookCache { 
     return BookCache() 
    } 
} 

Testez-le:

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

Vous devriez essayer AwesomeCache. Ses principales caractéristiques:

  • écrites à Swift
  • utilise sur le disque cache
  • soutenu par nscache pour des performances maximales et le soutien à l'expiration d'objets simples

Exemple:

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

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