Speichern von benutzerdefinierten Objekten in einem NSMutableArray in NSUserDefaults

101

Ich habe kürzlich versucht, die Suchergebnisse meiner iPhone-App in der NSUserDefaults-Sammlung zu speichern. Ich verwende dies auch, um Benutzerregistrierungsinformationen erfolgreich zu speichern, aber aus irgendeinem Grund ist der Versuch, mein NSMutableArray mit benutzerdefinierten Standortklassen zu speichern, immer leer.

Ich habe versucht, das NSMutableArray ab diesem Beitrag in ein NSData-Element zu konvertieren, erhalte jedoch das gleiche Ergebnis ( Kann ein ganzzahliges Array mit NSUserDefaults auf dem iPhone gespeichert werden? )

Die Codebeispiele, die ich ausprobiert habe, sind:

Sparen:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

oder

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

oder

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

Belastung:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

oder

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);  

oder

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Nachdem ich die folgenden Ratschläge befolgt habe, habe ich auch NSCoder in mein Objekt implementiert (ignorieren Sie die vorübergehende Überbeanspruchung von NSString):

#import "Location.h"


@implementation Location

@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:locationId forKey:@"locationId"];
    [coder encodeObject:companyName forKey:@"companyName"];
    [coder encodeObject:addressLine1 forKey:@"addressLine1"];
    [coder encodeObject:addressLine2 forKey:@"addressLine2"];
    [coder encodeObject:city forKey:@"city"];
    [coder encodeObject:postcode forKey:@"postcode"];
    [coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
    [coder encodeObject:description forKey:@"description"];
    [coder encodeObject:rating forKey:@"rating"];
    [coder encodeObject:priceGuide forKey:@"priceGuide"];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    [coder encodeObject:userLatitude forKey:@"userLatitude"];
    [coder encodeObject:userLongitude forKey:@"userLongitude"];
    [coder encodeObject:searchType forKey:@"searchType"];
    [coder encodeObject:searchId forKey:@"searchId"];
    [coder encodeObject:distance forKey:@"distance"];
    [coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
    [coder encodeObject:contentProviderId forKey:@"contentProviderId"];

}
Anthony Main
quelle

Antworten:

177

Um benutzerdefinierte Objekte in ein Array zu laden, habe ich Folgendes verwendet, um das Array abzurufen:

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        objectArray = [[NSMutableArray alloc] init];
}

Sie sollten überprüfen, ob die von den Benutzerstandards zurückgegebenen Daten nicht Null sind, da ich glaube, dass die Archivierung von Null einen Absturz verursacht.

Die Archivierung ist mit dem folgenden Code einfach:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

Wie f3lix hervorhob, müssen Sie sicherstellen, dass Ihr benutzerdefiniertes Objekt dem NSCoding-Protokoll entspricht. Das Hinzufügen von Methoden wie den folgenden sollte den Trick tun:

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

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [super init];
    if (self != nil)
    {
        label = [[coder decodeObjectForKey:@"label"] retain];
        numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
    }   
    return self;
}
Brad Larson
quelle
3
Am Ende Ihrer initWithCoder: -Methode fehlte Ihnen ein "return self". Das Hinzufügen scheint das Unarchivieren zu ermöglichen.
Brad Larson
2
Sie sollten Arrays nicht in nsuserdefaults speichern, da die Arrays für nsuserdefaults zu groß werden könnten. Es sollte stattdessen mit dem Pfad + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) in einer Datei gespeichert werden. Warum wird diese Antwort so oft angenommen?
mskw
1
@mskw - Sie haben Recht damit, dass NSUserDefaults nicht zum Speichern großer Arrays verwendet werden sollte (stattdessen sollten Core Data oder Raw SQLite verwendet werden), aber es ist vollkommen in Ordnung, kleine Arrays codierter Objekte dort zu verstauen. In jedem Fall wurde die Frage gestellt, wie dies zu tun ist, was der obige Code bietet. Was die Stimmen angeht, ist Ihre Vermutung so gut wie meine. Viele Menschen müssen dies in den letzten vier Jahren gewollt haben.
Brad Larson
2
@ Goodsum - Nein, das funktioniert gut. Probieren Sie es aus, es wird ein NSMutableArray zurückgeben, das tatsächlich veränderlich ist. Es startet lediglich das neue Array, indem es dieses Array von Elementen hinzufügt.
Brad Larson
1
@AlbertRenshaw - Woher kommt das, was andSyncSie dem obigen Code hinzugefügt haben? Dies ist keine tatsächliche Methodensignatur für NSUserDefaults. Auch synchronizeist nicht mehr so ​​nützlich wie früher: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson
13

Ich denke, Sie haben einen Fehler in Ihrer initWithCoder-Methode erhalten, zumindest im bereitgestellten Code geben Sie das 'self'-Objekt nicht zurück.

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

ich benutze

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

und

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

und es funktioniert perfekt für mich.

Mit freundlichen Grüßen

Stefan


quelle
3
Stephan du bist der Lebensretter, schau hier; Diese Zeile rettete mir den Verstand "if (self = [super init])" stackoverflow.com/questions/1933285/…
fyasar
Ich habe ERROR_BAD_ACCESS, aber du hast mich gerettet. Die Verwendung von Eigenschaft nur mit Beibehalten und Hinzufügen der self.variable löste es.
David
0

Ich denke, es sieht so aus, als ob das Problem mit Ihrem Code nicht darin besteht, das Ergebnisarray zu speichern. Es wird versucht, die Daten zu laden

lastResults = [prefs arrayForKey:@"lastResults"];

Dies gibt das durch den Schlüssel angegebene Array zurück.

Gcoop
quelle
0

Siehe "Warum konnte NSUserDefaults NSMutableDictionary nicht im iPhone SDK speichern?" ( Warum konnte NSUserDefaults NSMutableDictionary nicht im iPhone SDK speichern? )


Wenn Sie benutzerdefinierte Objekte (de) serialisieren möchten, müssen Sie die Funktionen zum (de) serialisieren der Daten (NSCoding-Protokoll) bereitstellen. Die Lösung, auf die Sie sich beziehen, funktioniert mit dem int-Array, da das Array kein Objekt, sondern ein zusammenhängender Speicherblock ist.

f3lix
quelle
Ich denke, Ihr Recht, ich werde einen Blick auf NSCoding werfen (es sei denn, Sie haben einen empfohlenen Link), die Objekte selbst bestehen aus NSStrings, das ist alles, also könnte ich einfach meinen eigenen Serialisierer schreiben und sie alle in ein String-Array einfügen
Anthony Main
Ok, ich habe NSCode implementiert, siehe oben, aber immer noch keine Freude mit NSArchiver
Anthony Main
0

Ich würde davon abraten, solche Dinge in der Standard-Datenbank zu speichern.

SQLite ist ziemlich einfach zu erlernen und zu verwenden. Ich habe eine Episode in einem meiner Screencasts ( http://pragprog.com/screencasts/v-bdiphone ) über einen einfachen Wrapper, den ich geschrieben habe (Sie können den Code erhalten, ohne den SC zu kaufen).

Es ist viel sauberer, App-Daten im App-Bereich zu speichern.

Alles, was gesagt wurde, wenn es immer noch Sinn macht, diese Daten in die Standarddatenbank zu stellen, dann lesen Sie den Beitrag f3lix veröffentlicht.

Bill Dudney
quelle
1
Vielen Dank für den Vorschlag, aber ich möchte nicht wirklich anfangen müssen, SQL-Abfragen (wie ich in Ihrem Beispiel sehe) nur für ein Objekt mit ein paar Zeichenfolgen
Anthony Main