Cómo almacenar objetos personalizados en NSUserDefaults

Resuelto Ethan Mick asked hace 54 años • 7 respuestas

Muy bien, he estado husmeando un poco y me doy cuenta de mi problema, pero no sé cómo solucionarlo. He creado una clase personalizada para contener algunos datos. Hago objetos para esta clase y necesito que duren entre sesiones. Antes estaba poniendo toda mi información en NSUserDefaults, pero esto no funciona.

-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.

Ese es el mensaje de error que recibo cuando coloco mi clase personalizada, "Reproductor", en el archivo NSUserDefaults. Ahora, he leído que aparentemente NSUserDefaultssolo almacena algunos tipos de información. Entonces, ¿cómo puedo introducir mis objetos NSUSerDefaults?

Leí que debería haber una manera de "codificar" mi objeto personalizado y luego insertarlo, pero no estoy seguro de cómo implementarlo, ¡agradecería la ayuda! ¡Gracias!

****EDITAR****

Muy bien, trabajé con el código que se proporciona a continuación (¡Gracias!), pero todavía tengo algunos problemas. Básicamente, el código falla ahora y no estoy seguro de por qué, porque no da ningún error. Quizás me falta algo básico y estoy demasiado cansado, pero ya veremos. Aquí está la implementación de mi clase personalizada, "Jugador":

@interface Player : NSObject {
    NSString *name;
    NSNumber *life;
    //Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;


- (id)init;

//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number    

@end

Archivo de implementación:

#import "Player.h"
@implementation Player
- (id)init {
    if (self = [super init]) {
        [self setName:@"Player Name"];
        [self setLife:[NSNumber numberWithInt:20]];
        [self setPsnCounters:[NSNumber numberWithInt:0]];
    }
    return self;
}

- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
    [input retain];
    if (name != nil) {
        [name release];
    }
    name = input;
}
- (void)setLife:(NSNumber *)input {
    [input retain];
    if (life != nil) {
        [life release];
    }
    life = input;
}
/* This code has been added to support encoding and decoding my objecst */

-(void)encodeWithCoder:(NSCoder *)encoder
{
    //Encode the properties of the object
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.life forKey:@"life"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if ( self != nil )
    {
        //decode the properties
        self.name = [decoder decodeObjectForKey:@"name"];
        self.life = [decoder decodeObjectForKey:@"life"];
    }
    return self;
}
-(void)dealloc {
    [name release];
    [life release];
    [super dealloc];
}
@end

Así que esa es mi clase, bastante sencilla, sé que funciona para crear mis objetos. Aquí están las partes relevantes del archivo AppDelegate (donde llamo a las funciones de cifrado y descifrado):

@class MainViewController;

@interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MainViewController *mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) MainViewController *mainViewController;

-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;


@end

Y luego las partes importantes del archivo de implementación:

    #import "MagicApp201AppDelegate.h"
    #import "MainViewController.h"
    #import "Player.h"

    @implementation MagicApp201AppDelegate


    @synthesize window;
    @synthesize mainViewController;


    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
        //First check to see if some things exist
        int startup = [prefs integerForKey:@"appHasLaunched"];
        if (startup == nil) {
//Make the single player 
        Player *singlePlayer = [[Player alloc] init];
        NSLog([[NSString alloc] initWithFormat:@"%@\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); //  test
        //Encode the single player so it can be stored in UserDefaults
        id test = [MagicApp201AppDelegate new];
        [test saveCustomObject:singlePlayer];
        [test release];
}
[prefs synchronize];
}

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
}

-(Player *)loadCustomObjectWithKey:(NSString*)key
{
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [prefs objectForKey:key ];
    Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
    return obj;
}

Eeee, perdón por todo el código. Sólo trato de ayudar. Básicamente, la aplicación se iniciará y luego fallará inmediatamente. Lo he reducido a la parte de cifrado de la aplicación, ahí es donde falla, así que estoy haciendo algo mal pero no estoy seguro de qué. Se agradecería la ayuda nuevamente, ¡gracias!

(Aún no he podido descifrar, ya que todavía no he conseguido que el cifrado funcione).

Ethan Mick avatar Jan 01 '70 08:01 Ethan Mick
Aceptado

En su clase Player, implemente los dos métodos siguientes (sustituyendo llamadas a encodeObject con algo relevante para su propio objeto):

- (void)encodeWithCoder:(NSCoder *)encoder {
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.question forKey:@"question"];
    [encoder encodeObject:self.categoryName forKey:@"category"];
    [encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if((self = [super init])) {
        //decode properties, other class vars
        self.question = [decoder decodeObjectForKey:@"question"];
        self.categoryName = [decoder decodeObjectForKey:@"category"];
        self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
    }
    return self;
}

Lectura y escritura de NSUserDefaults:

- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];

}

- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

Código tomado descaradamente de: clase de ahorro en nsuserdefaults

chrissr avatar Feb 23 '2010 03:02 chrissr

Swift 4 introdujo el protocolo Codable que hace toda la magia para este tipo de tareas. Simplemente ajuste su estructura/clase personalizada a ella:

struct Player: Codable {
  let name: String
  let life: Double
}

Y para almacenar en los valores predeterminados, puede usar PropertyListEncoder/Decoder :

let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)

let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)

También funcionará así para matrices y otras clases contenedoras de dichos objetos:

try! PropertyListDecoder().decode([Player].self, from: storedArray)
Sergiu Todirascu avatar Jan 01 '2018 21:01 Sergiu Todirascu

Creo una biblioteca RMMapper ( https://github.com/roomorama/RMMapper ) para ayudar a guardar objetos personalizados en NSUserDefaults de manera más fácil y conveniente, porque implementar encodeWithCoder e initWithCoder es súper aburrido.

Para marcar una clase como archivable, simplemente use:#import "NSObject+RMArchivable.h"

Para guardar un objeto personalizado en NSUserDefaults:

#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:@"SAVED_DATA"];

Para obtener un objeto personalizado de NSUserDefaults:

user = [defaults rm_customObjectForKey:@"SAVED_DATA"]; 
thomasdao avatar Jul 04 '2013 08:07 thomasdao