Warum konnte NSUserDefaults NSMutableDictionary nicht in iOS speichern?

145

Ich möchte ein NSMutableDictionaryObjekt in speichern NSUserDefaults. Der Schlüsseltyp in NSMutableDictionaryist NSStringder Werttyp NSArray, der eine Liste der zu implementierenden Objekte enthält NSCoding. Pro Dokument NSStringund NSArraybeide entsprechen NSCoding.

Ich erhalte diesen Fehler:

[NSUserDefaults setObject: forKey:]: Versuch, einen Nicht-Eigenschaftswert .... der Klasse NSCFDictionary einzufügen.

BlueDolphin
quelle

Antworten:

230

Ich habe eine Alternative herausgefunden, bevor ich speichere, codiere ich das Stammobjekt ( NSArrayObjekt) mit NSKeyedArchiver, das mit endet NSData. Verwenden Sie dann UserDefaults und speichern Sie die NSData.

Wenn ich die Daten benötige, lese ich sie aus NSDataund NSKeyedUnarchiverkonvertiere sie NSDatazurück zum Objekt.

Es ist ein wenig umständlich, weil ich NSDatajedes Mal zu / von konvertieren muss, aber es funktioniert einfach.

Hier ist ein Beispiel pro Anfrage:

Sparen:

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

Belastung:

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

Das Element im Array wird implementiert

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

Dann CommentItembietet bei der Implementierung von zwei Methoden:

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

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

Hat jemand eine bessere Lösung?

Vielen Dank an alle.

BlueDolphin
quelle
4
Haben Sie ein Codebeispiel, das Sie teilen können? Ich habe versucht, dies in der Post (537044) zu tun, aber ohne Freude
Anthony Main
Das hat bei mir gut funktioniert. Ich habe versucht, ein NSArray von NSDictionary-Objekten zu codieren, bei denen einige der Schlüssel keine Zeichenfolgen waren. Durch einfaches Verwenden von NSKeyedArchiver und Unarchiver auf dem NSArray wurde der Fehler "Versuch, einen Nicht-Eigenschaftswert einzufügen" beseitigt.
John Wright
Wann muss ich das geladene Objekt freigeben? Es wurde keine Zuordnung aufgerufen, daher würde ich davon ausgehen, dass sie automatisch freigegeben ist.
Marc
7
Möglicherweise müssen wir zum Speichern [Standardeinstellungen synchronisieren] hinzufügen
thanhbinh84
Die hier veröffentlichte Lösung funktioniert möglicherweise eine Weile, aber wenn Sie sich entscheiden, die Klasse zu entfernen oder zu ändern, werden Sie in ein Chaos verwickelt sein. Eine bessere Lösung besteht darin, Ihr Objekt dazu zu bringen, seinen Status mithilfe von NSNumbers, Strings usw. in ein Wörterbuch zu schreiben und diesen dann zu speichern. Zukunftssicher.
Tom Andersen
105

Wenn Sie ein Objekt in den Standardeinstellungen des Benutzers speichern, müssen alle Objekte rekursiv bis zum Ende nach unten Eigenschaftenlistenobjekte sein. Die Konformität mit NSCoding bedeutet hier nichts - codiert NSUserDefaultssie nicht automatisch NSData, Sie müssen dies selbst tun. Wenn Ihre "Liste der zu implementierenden NSCodingObjekte" Objekte bedeutet, die keine Objekte der Eigenschaftsliste sind, müssen Sie etwas damit tun, bevor Sie sie in den Benutzerstandards speichern.

Zu Ihrer Information der Eigenschaftsliste Klassen sind NSDictionary, NSArray, NSString, NSDate, NSData, und NSNumber. Sie können veränderbare Unterklassen (wie NSMutableDictionary) in Benutzereinstellungen schreiben, aber die Objekte, die Sie auslesen, sind immer unveränderlich.

Tom Harrington
quelle
61
Ich sollte auch erwähnen, dass ein NSDictionary nur dann ein Eigenschaftslistenobjekt ist, wenn die Schlüssel NSStrings sind. Im Allgemeinen können Sie andere Objekte als Wörterbuchschlüssel verwenden. Wenn jedoch Eigenschaftslisten erforderlich sind, müssen die Schlüssel Zeichenfolgen sein.
Tom Harrington
Ich bin auf dasselbe Problem gestoßen, als ich NSURL als Objekt in einem NSDictionary speichern und in den NSUserDefaults speichern wollte. Ich musste es in einen NSString ändern, als es funktionierte.
NicTesla
1
Toll! In meinem Fall habe ich versucht, NSNumber als Schlüssel für NSDictionary in den Benutzerstandards zu verwenden. Ich muss es in einen NSString ändern.
Joey
1
Solch ein verzögertes Verhalten führt dazu, dass unerfahrene Codierer ihr gesamtes Datenmodell als Wörterbücher schreiben, anstatt richtige Datenobjekte zu erstellen. Wie verdammt schwer kann es für Apple sein, Foundation-Klassen zu schreiben, die die Tatsache nutzen, dass obj-c Selbstbeobachtung hat und sich für uns um das Boxen und Unboxen kümmert?
ArtOfWarfare
14

Sind alle Ihre Schlüssel im Wörterbuch NSString? Ich denke, sie müssen es sein, um das Wörterbuch in einer Eigenschaftsliste zu speichern.

Sophie Alpert
quelle
1
Die Schlüssel sind NSString. Der Wert ist jedoch NSArray, dessen Element eine angepasste Klasse ist, die NSCoding implementiert. Es scheint, dass die Lösung nicht funktioniert, da UserDefaults nur 6 Klassentypen unterstützt, ohne NSCoding zu berücksichtigen.
BlueDolphin
8

Einfachste Antwort:

NSDictionaryist nur ein Plist-Objekt , wenn die Schlüssel NSStrings sind . Speichern Sie also den "Schlüssel" wie NSStringbei stringWithFormat.


Lösung:

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

Leistungen :

  1. Es wird ein String-Wert hinzugefügt .
  2. Es wird ein leerer Wert hinzugefügt, wenn Ihr Wert der Variablen ist NULL.
Bhavin
quelle
2
Ich hatte ein ähnliches Problem: Meine Schlüssel waren NSNumbers, die in Schlüsseln von Plist-Wörterbüchern anscheinend nicht erlaubt sind.
Mark Pauley
5

Haben Sie darüber nachgedacht, das NSCodingProtokoll umzusetzen ? Auf diese Weise können Sie auf dem iPhone mit zwei einfachen Methoden codieren und decodieren, die mit dem implementiert werden NSCoding. Zuerst müssten Sie das NSCodingzu Ihrer Klasse hinzufügen .

Hier ist ein Beispiel:

Dies ist in der .h-Datei

@interface GameContent : NSObject <NSCoding>

Dann müssen Sie zwei Methoden des NSCoding-Protokolls implementieren.

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

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

Weitere Informationen finden Sie in der Dokumentation zu NSCoder. Das hat sich für meine Projekte als sehr nützlich erwiesen, bei denen ich den Status der Anwendung auf dem iPhone speichern muss, wenn die Anwendung geschlossen ist, und sie wieder in den Status zurücksetzen muss, wenn sie wieder aktiviert ist.

Der Schlüssel besteht darin, das Protokoll zur Schnittstelle hinzuzufügen und dann die beiden Methoden zu implementieren, die Teil von sind NSCoding.

Ich hoffe das hilft!

Niels Hansen
quelle
0

Es gibt keine bessere Lösung. Eine andere Möglichkeit wäre, das codierte Objekt einfach auf der Festplatte zu speichern - aber das ist das Gleiche. Beide haben NSData, die dekodiert werden, wenn Sie sie zurückhaben möchten.

Dan Keen
quelle