Stockage d'objets personnalisés dans un NSMutableArray dans NSUserDefaults

101

J'ai récemment essayé de stocker les résultats de la recherche de mon application iPhone dans la collection NSUserDefaults. J'utilise également ceci pour enregistrer avec succès les informations d'enregistrement des utilisateurs, mais pour une raison quelconque, essayer de stocker mon NSMutableArray de classes d'emplacement personnalisées revient toujours vide.

J'ai essayé de convertir le NSMutableArray en un élément NSData à partir de cet article mais j'obtiens le même résultat ( possible de sauvegarder un tableau d'entiers en utilisant NSUserDefaults sur iPhone? )

Les exemples de code que j'ai essayés sont:

Sauver:

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

ou

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

ou

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

Charge:

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

ou

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

ou

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

Après avoir suivi les conseils ci-dessous, j'ai également implémenté NSCoder dans mon objet (ignorez la surutilisation de NSString son temporaire):

#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
la source

Réponses:

177

Pour charger des objets personnalisés dans un tableau, voici ce que j'ai utilisé pour saisir le tableau:

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

Vous devez vérifier que les données renvoyées par les valeurs par défaut de l'utilisateur ne sont pas nulles, car je pense que la désarchivage de nil provoque un plantage.

L'archivage est simple, en utilisant le code suivant:

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

Comme f3lix l'a souligné, vous devez rendre votre objet personnalisé conforme au protocole NSCoding. L'ajout de méthodes comme celles-ci devrait faire l'affaire:

- (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
la source
3
Il vous manquait un "return self" à la fin de votre méthode initWithCoder :. L'ajout de cela semble permettre la désarchivage.
Brad Larson
2
Vous ne devez pas stocker de tableaux dans nsuserdefaults car les tableaux pourraient devenir trop volumineux pour nsuserdefaults. Il doit être stocké à la place dans un fichier en utilisant + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) chemin. Pourquoi cette réponse est-elle si votée?
mskw
1
@mskw - Vous avez raison de dire que NSUserDefaults ne doit pas être utilisé pour le stockage de grandes baies (Core Data ou SQLite brut devrait être utilisé pour cela à la place), mais il est tout à fait correct de ranger de petits tableaux d'objets encodés ici. Dans tous les cas, la question était de savoir comment faire cela, ce que le code ci-dessus fournit. En ce qui concerne les votes, votre estimation est aussi bonne que la mienne. Beaucoup de gens ont dû vouloir faire cela au cours des quatre dernières années.
Brad Larson
2
@Goodsum - Non, cela fonctionne très bien. Essayez-le, il retournera un NSMutableArray qui est en effet mutable. Il démarre simplement le nouveau tableau en y ajoutant ce tableau d'éléments.
Brad Larson
1
@AlbertRenshaw - D'où vient le que andSyncvous avez ajouté au code ci-dessus? Ce n'est pas une véritable signature de méthode pour NSUserDefaults. De plus, ce synchronizen'est pas aussi utile qu'auparavant
Brad Larson
13

Je pense que vous avez eu une erreur dans votre méthode initWithCoder, au moins dans le code fourni, vous ne retournez pas l'objet '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


}

j'utilise

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

et

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

et cela fonctionne parfaitement pour moi.

Cordialement,

Stefan


la source
3
Stephan vous êtes le sauveur de vie, regardez ici; Cette ligne m'a sauvé l'esprit "if (self = [super init])" stackoverflow.com/questions/1933285
...
J'ai eu ERROR_BAD_ACCESS mais vous m'avez sauvé. Utilisait la propriété avec retenir uniquement et l'ajout de la variable self.variable l'a résolu.
David
0

Je pense qu'il semble que le problème avec votre code ne soit pas d'enregistrer le tableau des résultats. Son chargement des données essayez d'utiliser

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

Cela renverra le tableau spécifié par la clé.

Gcoop
la source
0

Voir «Pourquoi NSUserDefaults n’a pas pu enregistrer NSMutableDictionary dans le SDK iPhone?» ( Pourquoi NSUserDefaults n’a pas pu enregistrer NSMutableDictionary dans le SDK iPhone? )


Si vous souhaitez (dé) sérialiser des objets personnalisés, vous devez fournir les fonctions pour (dé) sérialiser les données (protocole NSCoding). La solution à laquelle vous faites référence fonctionne avec le tableau int car le tableau n'est pas un objet mais un bloc de mémoire contigu.

f3lix
la source
Je pense que vous avez raison, je vais jeter un coup d'œil sur NSCoding (à moins que vous n'ayez un lien recommandé), les objets eux-mêmes sont constitués sur NSStrings, c'est tout, donc je suppose que je pourrais simplement écrire mon propre sérialiseur et les mettre tous dans un tableau de chaînes
Anthony Main le
Ok donc j'ai implémenté NSCode voir ci-dessus, mais toujours pas de joie en utilisant NSArchiver
Anthony Main
0

Je recommanderais de ne pas essayer de stocker des éléments comme celui-ci dans la base de données par défaut.

SQLite est assez facile à prendre en main et à utiliser. J'ai un épisode dans l'un de mes screencasts ( http://pragprog.com/screencasts/v-bdiphone ) sur un simple wrapper que j'ai écrit (vous pouvez obtenir le code sans acheter le SC).

Il est beaucoup plus simple de stocker les données d'application dans l'espace d'application.

Cela dit, si cela a encore du sens de mettre ces données dans la base de données par défaut, alors voir le post f3lix posté.

Bill Dudney
la source
1
Merci pour la suggestion mais je ne veux pas vraiment avoir à commencer à écrire des requêtes SQL (comme je le vois dans votre exemple) juste pour un objet avec quelques chaînes
Anthony Main