2010-12-27 14 views
12

Estoy escribiendo una aplicación que necesita mantener un caché en memoria de un grupo de objetos, pero eso no se va de las manos así que estoy planeando usar NSCache para almacenarlo todo . Parece que se encargará de purgar y eso para mí, que es fantástico.Guardar NSCache contenido en disco

También me gustaría conservar el caché entre lanzamientos, por lo que debo escribir los datos del caché en el disco. ¿Hay alguna manera fácil de guardar los contenidos de NSCache en un plist o algo así? ¿Hay quizás mejores formas de lograr esto usando algo que no sea NSCache?

Esta aplicación va a estar en el iPhone, así que voy a necesitar sólo las clases que están disponibles en iOS 4+ y no sólo OS X.

Gracias!

Respuesta

13

Estoy escribiendo una aplicación que necesita para mantener una caché en memoria de un montón de objetos , pero eso no quiere salir de la mano, así que estoy planeando sobre el uso NSCache para almacenar todo. Parece que se encargará de purgar y eso para mí, , que es fantástico. También me gustaría conservar el caché entre lanzamientos, así que tengo que escribir los datos de caché en el disco. ¿Hay una forma fácil de guardar el contenido NSCache en un plist o algo así? ¿Hay quizás mejores formas de lograr esto utilizando algo que no sea NSCache?

Acaba de describir exactamente lo que CoreData hace; persistencia de gráficos de objetos con capacidades de purga y poda.

NSCache no está diseñado para ayudar con la persistencia.

Dado que sugirió persistir a un formato plist, usar Core Data en su lugar no es una gran diferencia conceptual.

+0

He utilizado Core Data en casi todas las aplicaciones de bases de datos que he creado, pero parece que esta no es la mejor opción. Estoy usando los cachés para almacenar los resultados de la API, manteniendo la aplicación en estado activo durante el lanzamiento y al cargar elementos que ya se han cargado. Me preocupa que la base de datos central se salga de control y siga creciendo. Simplemente no parece que los datos centrales se utilicen mejor para el almacenamiento en caché de datos "temporales". La persistencia que necesito es básicamente una función secundaria de la funcionalidad de caché en memoria que necesito, y esa es la razón por la que me estaba inclinando hacia NSCache. –

+0

Yah - Puedo ver tu enigma. NSCoding no es * tan * difícil de implementar, siempre se puede ir por ese camino. Entonces la pregunta es cuándo escribir/actualizar la versión persistente de la memoria caché. En mi experiencia, es bastante fácil ir por un camino que termina por reinventar la rueda de la persistencia con todas sus complejidades. Y, por supuesto, la aplicación de mejor rendimiento es la que se envía primero. ;) – bbum

+0

@CoryImdieke, ahora estamos enfrentando la misma situación y estoy planeando usar NSCache. ¿Solo me pregunto qué solución elegiste? – Koolala

9

use TMCache (https://github.com/tumblr/TMCache). Es como NSCache pero con persistencia y depuración de caché. Escrito por el equipo de Tumblr.

+1

Lamentablemente, ya no se mantiene activamente:/ – manicaesar

+0

La mejor respuesta ahora es utilizar CoreData o Realm. Coredata es más estándar, Reino más fácil de aprender. –

0

A veces puede ser más conveniente no tratar con Datos centrales y solo para guardar el contenido de la memoria caché en el disco. Puede lograr esto con NSKeyedArchiver y UserDefaults (estoy usando Swift 3.0.2 en ejemplos de código a continuación).

primero vamos a abstraer de NSCache e imaginamos que queremos ser capaces de persistir cualquier caché que se ajusta al protocolo:

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 tipo asociado tiene que ser Hashable porque eso es requisito para el parámetro de tipo Set.

A continuación tenemos que poner en práctica para NSCodingCache usando clase de ayuda 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) 
    } 
} 

aquí:

  • C es el tipo que se ajusta a Cache.
  • C.Key tipo asociado tiene que ajustarse a:
    • Swift CustomStringConvertible protocolo para ser convertible a String porque NSCoder.encode(forKey:) método acepta String para el parámetro clave.
    • Swift ExpressibleByStringLiteral protocolo para convertir [String] de nuevo a Set<Key>
  • Necesitamos convertir Set<Key> a [String] y almacenarla a NSCoder con keys clave, ya que no hay manera de extraer durante la decodificación de NSCoder claves que se utilizaron cuando se codifica objetos. Pero puede haber una situación en la que también tenemos entrada en la memoria caché con la clave keys, por lo que para distinguir las claves de caché de la clave especial keys prefijimos las claves de caché con _.
  • C.Value tipo asociado tiene que ajustarse a NSCodingConvertible protocolo para obtener NSCoding casos de los valores almacenados en la memoria caché:

    protocol NSCodingConvertible { 
        associatedtype Coding: NSCoding 
    
        var coding: Coding { get } 
    } 
    
  • Value.Coding tiene que ajustarse a ValueProvider protocolo porque es necesario para obtener los valores de vuelta de NSCoding casos:

    protocol ValueProvider { 
        associatedtype Value 
    
        var value: Value { get } 
    } 
    
  • C.Value.Coding.Value y C.Value tienen que ser equivalentes becau es el valor del que obtenemos la instancia NSCoding cuando la codificación debe tener el mismo tipo que el valor que obtenemos de NSCoding al decodificar.

  • CB es un tipo que satisfaga Builder protocolo y ayuda a crear la instancia de memoria caché de C Tipo:

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

A continuación vamos a hacer NSCache se ajustan a Cache protocolo. Aquí tenemos un problema. NSCache tiene el mismo problema que NSCoder, no proporciona la forma de extraer claves para objetos almacenados. Hay tres maneras de solucionar este:

  1. Wrap NSCache con el tipo de costumbre que mantiene claves Set y usarlo en todas partes en lugar 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 usted todavía tiene que pasar NSCache algún lugar entonces puede intentar extenderlo en Objective-C haciendo lo mismo que hice anteriormente con BetterCache.

  3. Utilice alguna otra implementación de caché.

Ahora usted tiene el tipo que se ajusta al protocolo Cache y ya está listo para usarlo.

Vamos a definir el tipo Book qué instancias vamos a almacenar en caché y NSCoding para ese tipo:

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

Algunos typealiases para una mejor legibilidad:

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

y constructor que nos ayudará a crear una instancia de Cache instancia:

class BookCacheBuilder: Builder { 
    required init() { 
    } 

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

prueba que:

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

Usted debe tratar AwesomeCache. Sus principales características:

  • escritos en Swift
  • utiliza en el disco de almacenamiento en caché
  • respaldado por NSCache para un máximo rendimiento y el apoyo a la expiración de los objetos individuales

Ejemplo:

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

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