Como armazenar objects personalizados em NSUserDefaults

Tudo bem, então eu tenho andado por aí, e percebo o meu problema, mas não sei como consertar. Eu fiz uma class personalizada para armazenar alguns dados. Eu faço objects para esta class, e eu preciso deles para durar entre as sessões. Antes eu estava colocando todas as minhas informações em NSUserDefaults , mas isso não está funcionando.

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

Essa é a mensagem de erro que recebo quando coloco minha class personalizada, “Player”, no NSUserDefaults . Agora, eu li que aparentemente NSUserDefaults armazena apenas alguns tipos de informação. Então, como obtenho meus objects em NSUSerDefaults ?

Eu li que deveria haver uma maneira de “codificar” meu object personalizado e depois colocá-lo, mas não tenho certeza de como implementá-lo, a ajuda seria apreciada! Obrigado!

****EDITAR****

Tudo bem, então eu trabalhei com o código abaixo (obrigado!), Mas ainda estou tendo alguns problemas. Basicamente, o código trava agora e não sei porque, porque não dá nenhum erro. Talvez eu esteja sentindo falta de algo básico e estou cansado demais, mas vamos ver. Aqui está a implementação da minha class Custom, “Player”:

 @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 

Arquivo de Implementação:

 #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 

Então essa é minha aula, bem direta, eu sei que funciona para fazer meus objects. Então, aqui estão as partes relevantes do arquivo AppDelegate (onde eu chamo as funções de criptografia e descriptografia):

 @class MainViewController; @interface MagicApp201AppDelegate : NSObject  { UIWindow *window; MainViewController *mainViewController; } @property (nonatomic, retain) IBOutlet UIWindow *window; @property (nonatomic, retain) MainViewController *mainViewController; -(void)saveCustomObject:(Player *)obj; -(Player *)loadCustomObjectWithKey:(NSString*)key; @end 

E então as partes importantes do arquivo de implementação:

  #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, desculpe por todo o código. Apenas tentando ajudar. Basicamente, o aplicativo será iniciado e, em seguida, será bloqueado imediatamente. Eu reduzi isso à parte de criptografia do aplicativo, é aí que ele cai, então estou fazendo algo errado, mas não tenho certeza do que. Ajuda seria apreciada novamente, obrigado!

(Eu não tenho conseguido descriptografar ainda, como eu não tenho criptografia funcionando ainda.)

Em sua class Player, implemente os dois methods a seguir (replace chamadas para encodeObject por algo relevante para seu próprio object):

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

Lendo e escrevendo 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 descaradamente emprestado de: saving class in nsuserdefaults

Eu crio uma biblioteca RMMapper ( https://github.com/roomorama/RMMapper ) para ajudar a salvar o object personalizado em NSUserDefaults mais fácil e mais conveniente, porque a implementação de encodeWithCoder e initWithCoder é super chata!

Para marcar uma class como arquivável, use: #import "NSObject+RMArchivable.h"

Para salvar um object personalizado em NSUserDefaults:

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

Para obter obj personalizado de NSUserDefaults:

 user = [defaults rm_customObjectForKey:@"SAVED_DATA"]; 

Se alguém está procurando por uma versão rápida:

1) Crie uma class personalizada para seus dados

 class customData: NSObject, NSCoding { let name : String let url : String let desc : String init(tuple : (String,String,String)){ self.name = tuple.0 self.url = tuple.1 self.desc = tuple.2 } func getName() -> String { return name } func getURL() -> String{ return url } func getDescription() -> String { return desc } func getTuple() -> (String,String,String) { return (self.name,self.url,self.desc) } required init(coder aDecoder: NSCoder) { self.name = aDecoder.decodeObjectForKey("name") as! String self.url = aDecoder.decodeObjectForKey("url") as! String self.desc = aDecoder.decodeObjectForKey("desc") as! String } func encodeWithCoder(aCoder: NSCoder) { aCoder.encodeObject(self.name, forKey: "name") aCoder.encodeObject(self.url, forKey: "url") aCoder.encodeObject(self.desc, forKey: "desc") } } 

2) Para salvar os dados, use a seguinte function:

 func saveData() { let data = NSKeyedArchiver.archivedDataWithRootObject(custom) let defaults = NSUserDefaults.standardUserDefaults() defaults.setObject(data, forKey:"customArray" ) } 

3) Para recuperar:

 if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData { custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData] } 

Nota: Aqui estou salvando e recuperando uma matriz dos objects de class personalizados.

Swift 4

Introduziu o protocolo codificável que faz toda a magia para esses tipos de tarefas. Basta adequar sua estrutura / class personalizada a ela:

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

E para armazenar nos padrões você pode usar o 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) 

Ele funcionará assim para matrizes e outras classs de contêineres de tais objects também:

 try! PropertyListDecoder().decode([Player].self, from: storedArray) 

Tomando a resposta de @ chrissr e rodando com ele, este código pode ser implementado em uma boa categoria em NSUserDefaults para salvar e recuperar objects personalizados:

 @interface NSUserDefaults (NSUserDefaultsExtensions) - (void)saveCustomObject:(id)object key:(NSString *)key; - (id)loadCustomObjectWithKey:(NSString *)key; @end @implementation NSUserDefaults (NSUserDefaultsExtensions) - (void)saveCustomObject:(id)object key:(NSString *)key { NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object]; [self setObject:encodedObject forKey:key]; [self synchronize]; } - (id)loadCustomObjectWithKey:(NSString *)key { NSData *encodedObject = [self objectForKey:key]; id object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject]; return object; } @end 

Uso:

 [[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:@"myKey"]; 

Sincronize os dados / objects que você salvou em NSUserDefaults

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

Espero que isso ajude você. obrigado

Swift 3

 class MyObject: NSObject, NSCoding { let name : String let url : String let desc : String init(tuple : (String,String,String)){ self.name = tuple.0 self.url = tuple.1 self.desc = tuple.2 } func getName() -> String { return name } func getURL() -> String{ return url } func getDescription() -> String { return desc } func getTuple() -> (String, String, String) { return (self.name,self.url,self.desc) } required init(coder aDecoder: NSCoder) { self.name = aDecoder.decodeObject(forKey: "name") as? String ?? "" self.url = aDecoder.decodeObject(forKey: "url") as? String ?? "" self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? "" } func encode(with aCoder: NSCoder) { aCoder.encode(self.name, forKey: "name") aCoder.encode(self.url, forKey: "url") aCoder.encode(self.desc, forKey: "desc") } } 

para armazenar e recuperar:

 func save() { let data = NSKeyedArchiver.archivedData(withRootObject: object) UserDefaults.standard.set(data, forKey:"customData" ) } func get() -> MyObject? { guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil } return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject }