Pourquoi NSUserDefaults n'a pas réussi à enregistrer NSMutableDictionary dans iOS?

145

Je souhaite enregistrer un NSMutableDictionaryobjet dans NSUserDefaults. Le type de clé NSMutableDictionaryest NSString, le type de valeur est NSArray, qui contient une liste d'objets qui implémentent NSCoding. Par document, NSStringet les NSArraydeux sont conformes NSCoding.

J'obtiens cette erreur:

[NSUserDefaults setObject: forKey:]: Tentative d'insertion de valeur non-propriété .... de classe NSCFDictionary.

BleuDolphin
la source

Réponses:

230

J'ai trouvé une alternative, avant de sauvegarder, j'encode l'objet racine ( NSArrayobjet) en utilisant NSKeyedArchiver, qui se termine par NSData. Utilisez ensuite UserDefaults pour enregistrer le fichier NSData.

Lorsque j'ai besoin des données, je lis le NSData, et je l' utilise NSKeyedUnarchiverpour le reconvertir NSDataen objet.

C'est un peu encombrant, car j'ai besoin de convertir vers / depuis NSDatachaque fois, mais cela fonctionne.

Voici un exemple par demande:

Sauver:

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

Charge:

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

L'élément du tableau implémente

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

Ensuite, dans la mise en œuvre de CommentItem, fournit deux méthodes:

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

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

Quelqu'un a une meilleure solution?

Merci tout le monde.

BleuDolphin
la source
4
Avez-vous un exemple de code que vous pouvez partager, j'ai essayé de le faire dans le post (537044) mais sans joie
Anthony Main
Cela a bien fonctionné pour moi. J'essayais d'encoder un NSArray d'objets NSDictionary où certaines des clés n'étaient pas des chaînes. Le simple fait d'utiliser NSKeyedArchiver et Unarchiver sur NSArray a éliminé l'erreur «Tentative d'insérer une valeur sans propriété».
John Wright
Quand dois-je libérer l'objet chargé? Aucun alloc n'a été appelé, donc je suppose qu'il est automatiquement déclenché?
Marc
7
nous pourrions avoir besoin d'ajouter [synchroniser par défaut] pour enregistrer
thanhbinh84
La solution publiée ici peut fonctionner pendant un certain temps, mais lorsque vous décidez de supprimer ou de modifier la classe, vous vous retrouverez enfermé dans un désordre. Une meilleure solution est de faire en sorte que votre objet écrive son état en utilisant NSNumbers, Strings, etc. dans un dictionnaire, puis stockez-le. Preuve du futur.
Tom Andersen
105

Si vous enregistrez un objet dans les valeurs par défaut de l'utilisateur, tous les objets, récursivement, jusqu'en bas, doivent être des objets de liste de propriétés. Se conformer à NSCoding ne veut rien dire ici - NSUserDefaultsne les encodera pas automatiquement NSData, vous devez le faire vous-même. Si votre "liste d'objets qui implémente NSCoding" signifie des objets qui ne sont pas des objets de liste de propriétés, alors vous devrez faire quelque chose avec eux avant d'enregistrer les valeurs par défaut de l'utilisateur.

Pour votre information , les classes de la liste des biens sont NSDictionary, NSArray, NSString, NSDate, NSDataet NSNumber. Vous pouvez écrire des sous-classes modifiables (comme NSMutableDictionary) dans les préférences de l'utilisateur, mais les objets que vous lisez seront toujours immuables.

Tom Harrington
la source
61
Je dois également mentionner qu'un NSDictionary n'est qu'un objet de liste de propriétés si les clés sont des NSStrings. En général, vous pouvez utiliser d'autres objets comme clés de dictionnaire, mais lorsque des listes de propriétés sont requises, les clés doivent être des chaînes.
Tom Harrington
Je suis tombé sur le même problème, lorsque je voulais stocker NSURL en tant qu'objet dans un NSDictionary et le stocker dans NSUserDefaults. J'ai dû le changer en NSString, que cela a fonctionné.
NicTesla
1
Génial! Dans mon cas, j'ai essayé d'utiliser NSNumber comme clé de NSDictionary dans les valeurs par défaut de l'utilisateur. Je dois le changer en NSString.
Joey
1
Un tel comportement retardé est ce qui conduit les codeurs inexpérimentés à écrire l'intégralité de leur modèle de données sous forme de dictionnaires au lieu de créer des objets de données appropriés? À quel point peut-il être difficile pour Apple d'écrire des classes de base qui utilisent le fait qu'obj-c a une introspection et s'occupent de la boxe et du déballage pour nous?
ArtOfWarfare
14

Toutes vos clés sont-elles dans le dictionnaire NSString? Je pense qu'ils doivent l'être pour enregistrer le dictionnaire dans une liste de propriétés.

Sophie Alpert
la source
1
les clés sont NSString. mais la valeur est NSArray, dont l'élément est une classe personnalisée qui implémente NSCoding. Il semble que cette solution ne fonctionne pas car UserDefaults ne prend en charge que 6 types de classe, et non NSCoding.
BlueDolphin
8

Réponse la plus simple:

NSDictionaryest uniquement un objet plist , si les clés sont des NSStrings . Alors, stockez la "clé" comme NSStringavec stringWithFormat.


Solution :

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

Avantages :

  1. Il ajoutera String-Value .
  2. Il ajoutera une valeur vide lorsque votre valeur de variable est NULL.
Bhavin
la source
2
J'ai eu un problème similaire: mes clés étaient des NSNumbers, qui ne sont apparemment pas autorisés dans les clés des dictionnaires plist.
Mark Pauley
5

Avez-vous envisagé de mettre en œuvre le NSCodingProtocole? Cela vous permettra d'encoder et de décoder sur l'iPhone avec deux méthodes simples implémentées avec le NSCoding. Vous devez d'abord ajouter le NSCodingà votre classe.

Voici un exemple:

C'est dans le fichier .h

@interface GameContent : NSObject <NSCoding>

Ensuite, vous devrez implémenter deux méthodes du protocole 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"];
    }

Consultez la documentation sur NSCoder pour plus d'informations. Cela s'est avéré très pratique pour mes projets où je dois enregistrer l'état de l'application sur l'iPhone si l'application est fermée et la restaurer à son état lorsqu'elle est de retour.

La clé est d'ajouter le protocole à l'interface, puis d'implémenter les deux méthodes qui font partie de NSCoding.

J'espère que ça aide!

Niels Hansen
la source
0

Il n'y a pas de meilleure solution. Une autre option serait de simplement enregistrer l'objet codé sur le disque - mais cela fait la même chose. Ils se retrouvent tous les deux avec NSData qui est décodé lorsque vous le souhaitez.

Dan Keen
la source