Archiviazione di oggetti personalizzati in un NSMutableArray in NSUserDefaults


101

Recentemente ho provato a memorizzare i risultati della ricerca della mia app per iPhone nella raccolta NSUserDefaults. Lo uso anche per salvare correttamente le informazioni di registrazione dell'utente, ma per qualche motivo il tentativo di memorizzare il mio NSMutableArray di classi Location personalizzate torna sempre vuoto.

Ho provato a convertire NSMutableArray in un elemento NSData a partire da questo post ma ottengo lo stesso risultato (è possibile salvare un array intero utilizzando NSUserDefaults su iPhone? )

Gli esempi di codice che ho provato sono:

Salva:

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

o

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

o

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

Caricare:

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

o

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

o

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

Dopo aver seguito i consigli di seguito, ho anche implementato NSCoder nel mio oggetto (ignora l'uso eccessivo di NSString è temporaneo):

#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"];

}

Risposte:


177

Per caricare oggetti personalizzati in un array, questo è ciò che ho usato per afferrare l'array:

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];
}

Dovresti controllare che i dati restituiti dalle impostazioni predefinite dell'utente non siano nulli, perché credo che l'annullamento dell'archiviazione da zero provochi un arresto anomalo.

L'archiviazione è semplice, utilizzando il codice seguente:

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

Come sottolineato da f3lix, devi rendere il tuo oggetto personalizzato conforme al protocollo NSCoding. L'aggiunta di metodi come il seguente dovrebbe fare il trucco:

- (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;
}

3
Ti mancava un "return self" alla fine del tuo metodo initWithCoder:. L'aggiunta di ciò sembra consentire l'annullamento dell'archiviazione.
Brad Larson

2
Non dovresti memorizzare gli array in nsuserdefaults poiché gli array potrebbero diventare troppo grandi per nsuserdefaults. Dovrebbe essere invece memorizzato in un file usando + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) path. Perché questa risposta è stata votata così tanto?
mskw

1
@mskw - Hai ragione nel dire che NSUserDefaults non dovrebbe essere usato per l'archiviazione di array di grandi dimensioni (dovrebbero essere usati Core Data o raw SQLite), ma è perfettamente corretto riporre lì piccoli array di oggetti codificati. In ogni caso, la domanda era chiedersi come farlo, che è ciò che fornisce il codice sopra. Per quanto riguarda i voti, la tua ipotesi è buona quanto la mia. Molte persone devono aver voluto farlo negli ultimi quattro anni.
Brad Larson

2
@ Goodsum - No, funziona benissimo. Provalo, restituirà un NSMutableArray che è effettivamente modificabile. Avvia semplicemente il nuovo array aggiungendovi quell'array di elementi.
Brad Larson

1
@AlbertRenshaw - Da dove viene andSyncil codice che hai aggiunto al codice sopra? Non è una firma del metodo effettiva per NSUserDefaults. Inoltre, synchronizenon è utile come una volta: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

Penso che tu abbia ricevuto un errore nel tuo metodo initWithCoder, almeno nel codice fornito non restituisci l'oggetto 'self'.

- (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


}

Io uso

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

e

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];
}

e funziona perfettamente per me.

Per quanto riguarda,

Stefan


3
Stephan tu sei il salvavita, guarda qui; Quella linea ha salvato la mia mente "se (self = [super init])" stackoverflow.com/questions/1933285/...
fyasar

Ho ERROR_BAD_ACCESS ma mi hai salvato. Stavo usando la proprietà con solo ritenzione e aggiungendo la variabile self.
David

0

Penso che il problema con il tuo codice non sia il salvataggio dell'array dei risultati. Sta caricando i dati, prova a usare

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

Ciò restituirà l'array specificato dalla chiave.


0

Vedi "Perché NSUserDefaults non è riuscito a salvare NSMutableDictionary in iPhone SDK?" ( Perché NSUserDefaults non è riuscito a salvare NSMutableDictionary in iPhone SDK? )


Se vuoi (de) serializzare oggetti personalizzati, devi fornire le funzioni per (de) serializzare i dati (protocollo NSCoding). La soluzione a cui fai riferimento funziona con l'array int perché l'array non è un oggetto ma un blocco contiguo di memoria.


Penso che tu abbia ragione, darò un'occhiata a NSCoding (a meno che tu non abbia un linky consigliato), gli oggetti stessi sono costituiti su NSStrings che è tutto quindi suppongo che potrei semplicemente scrivere il mio serializzatore e metterli tutti in un array di stringhe
Anthony Main,

Ok, quindi ho implementato NSCode vedi sopra, ma ancora nessuna gioia usando NSArchiver
Anthony Main

0

Consiglierei di non provare a memorizzare cose come questa nel db predefinito.

SQLite è abbastanza facile da raccogliere e utilizzare. Ho un episodio in uno dei miei screencast ( http://pragprog.com/screencasts/v-bdiphone ) su un semplice wrapper che ho scritto (puoi ottenere il codice senza acquistare l'SC).

È molto più pulito archiviare i dati delle app nello spazio app.

Detto questo, se ha ancora senso inserire questi dati nel database predefinito, vedere il post pubblicato su f3lix.


1
Grazie per il suggerimento ma non voglio davvero dover iniziare a scrivere query SQL (come vedo nel tuo esempio) solo per un oggetto con poche stringhe
Anthony Main
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.