¿Por qué NSUserDefaults no pudo guardar NSMutableDictionary en iOS?

145

Me gustaría guardar un NSMutableDictionaryobjeto en NSUserDefaults. El tipo de clave NSMutableDictionaryes NSString, el tipo de valor es NSArray, que contiene una lista de objetos que implementa NSCoding. Por documento, NSStringy NSArrayambos son conformes a NSCoding.

Recibo este error:

[NSUserDefaults setObject: forKey:]: Intenta insertar un valor que no sea de propiedad ... de la clase NSCFDictionary.

BlueDolphin
fuente

Respuestas:

230

Descubrí una alternativa, antes de guardar, codifico el objeto raíz ( NSArrayobjeto) usando NSKeyedArchiver, que termina con NSData. Luego use UserDefaults y guarde el NSData.

Cuando necesito los datos, leo NSDatay utilizo NSKeyedUnarchiverpara NSDatavolver a convertir el objeto.

Es un poco engorroso, porque necesito convertir a / de NSDatacada vez, pero simplemente funciona.

Aquí hay un ejemplo por solicitud:

Salvar:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arr = ... ; // set value
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[defaults setObject:data forKey:@"theKey"];
[defaults synchronize];

Carga:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:@"theKey"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];

El elemento en la matriz implementa

@interface CommentItem : NSObject<NSCoding> {
    NSString *value;
}

Luego, en la implementación de CommentItem, proporciona dos métodos:

-(void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:value forKey:@"Value"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self.value = [decoder decodeObjectForKey:@"Value"];
    return self;
}

¿Alguien tiene una mejor solución?

Gracias a todos.

BlueDolphin
fuente
44
¿Tiene una muestra de código que pueda compartir? He estado tratando de hacer esto en la publicación (537044) pero sin alegría
Anthony Main
Esto funcionó muy bien para mí. Estaba tratando de codificar un NSArray de objetos NSDictionary donde algunas de las claves no eran cadenas. Simplemente usando NSKeyedArchiver y Unarchiver en el NSArray se libró del error "Intento de insertar un valor que no es de propiedad".
John Wright el
¿Cuándo necesito liberar el objeto cargado? No se llamó a alloc, por lo que supongo que es autorelased?
Marc
77
podríamos necesitar agregar [sincronización predeterminada] para guardar
thanhbinh84
La solución publicada aquí puede funcionar por un tiempo, pero cuando decida eliminar o cambiar la clase, se encontrará en un lío. Una mejor solución es hacer que su objeto escriba su estado usando NSNumbers, Strings, etc. en un diccionario, luego almacénelo. Prueba del futuro.
Tom Andersen
105

Si está guardando un objeto en los valores predeterminados del usuario, todos los objetos, recursivamente, hasta el final, deben ser objetos de la lista de propiedades. Cumplir con NSCoding no significa nada aquí: NSUserDefaultsno los codificará automáticamente NSData, debe hacerlo usted mismo. Si su "lista de objetos que implementa NSCoding" significa objetos que no son objetos de la lista de propiedades, entonces tendrá que hacer algo con ellos antes de guardarlos en los valores predeterminados del usuario.

FYI las clases de lista propiedad son NSDictionary, NSArray, NSString, NSDate, NSData, y NSNumber. Puede escribir subclases mutables (como NSMutableDictionary) a las preferencias del usuario, pero los objetos que lee siempre serán inmutables.

Tom Harrington
fuente
61
También debo mencionar que un NSDictionary es solo un objeto de lista de propiedades si las claves son NSStrings. En general, puede usar otros objetos como claves de diccionario, pero cuando se requieren listas de propiedades, las claves deben ser cadenas.
Tom Harrington el
Me encontré con el mismo problema, cuando quería almacenar NSURL como un objeto en un NSDictionary y almacenarlo en NSUserDefaults. Tuve que cambiarlo a un NSString, de lo que funcionó.
NicTesla
1
¡Excelente! En mi caso, intenté usar NSNumber como una clave de NSDictionary en los valores predeterminados del usuario. Tengo que cambiarlo a un NSString.
Joey
1
Tal comportamiento retardado es lo que lleva a los codificadores inexpertos a escribir todo su modelo de datos como diccionarios en lugar de crear objetos de datos adecuados. ¿Qué tan difícil puede ser para Apple escribir clases básicas que utilicen el hecho de que obj-c tiene introspección y se encarga del boxeo y el desempaquetado?
ArtOfWarfare
14

¿Están todas sus claves en el diccionario NSString? Creo que tienen que estarlo para guardar el diccionario en una lista de propiedades.

Sophie Alpert
fuente
1
Las claves son NSString. pero el valor es NSArray, cuyo elemento es una clase personalizada que implementa NSCoding. Parece que la solución no funciona porque UserDefaults solo admite 6 tipos de clase, no considere NSCoding.
BlueDolphin
8

La respuesta más simple:

NSDictionaryes solo un objeto plist , si las claves son NSStrings . Por lo tanto, guarde la "clave" como NSStringcon stringWithFormat.


Solución:

NSString *key = [NSString stringWithFormat:@"%@",[dictionary valueForKey:@"Key"]];

Beneficios:

  1. Agregará valor de cadena .
  2. Agregará un valor vacío cuando su valor de variable sea NULL.
Bhavin
fuente
2
Tuve un problema similar: mis claves eran NSNumbers, que aparentemente no están permitidas en las claves de los diccionarios plist.
Mark Pauley
5

¿Has pensado en implementar el NSCodingProtocolo? Esto le permitirá codificar y decodificar en el iPhone con dos métodos simples que se implementan con el NSCoding. Primero necesitaría agregar el NSCodinga su clase.

Aquí hay un ejemplo:

Esto está en el archivo .h

@interface GameContent : NSObject <NSCoding>

Entonces deberá implementar dos métodos del Protocolo NSCoding.

    - (id) initWithCoder: (NSCoder *)coder
    {
        if (self = [super init])
        {
               [self setFoundHotSpots:[coder decodeObjectForKey:@"foundHotSpots"]];
        }
        return self;
    }

    - (void) encodeWithCoder: (NSCoder *)coder
    {
           [coder encodeObject:foundHotSpots forKey:@"foundHotSpots"];
    }

Consulte la documentación en NSCoder para obtener más información. Eso ha sido muy útil para mis proyectos en los que necesito guardar el estado de la aplicación en el iPhone si la aplicación está cerrada y restaurarla a su estado cuando está nuevamente activada.

La clave es agregar el protocolo a la interfaz y luego implementar los dos métodos que forman parte NSCoding.

¡Espero que esto ayude!

Niels Hansen
fuente
0

No hay mejor solución. Otra opción sería simplemente guardar el objeto codificado en el disco, pero eso es lo mismo. Ambos terminan con NSData que se decodifica cuando quieres recuperarlo.

Dan Keen
fuente