web-dev-qa-db-fra.com

Comment stocker des objets personnalisés dans NSUserDefaults

Bon, j'ai donc fait quelques recherches et je me suis rendu compte de mon problème, mais je ne sais pas comment le résoudre. J'ai créé une classe personnalisée pour contenir des données. Je fabrique des objets pour cette classe et je dois les faire durer entre les sessions. Avant, je mettais toutes mes informations dans NSUserDefaults, mais cela ne fonctionne pas.

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

C'est le message d'erreur que je reçois lorsque je mets ma classe personnalisée, "Player", dans le fichier NSUserDefaults. J'ai lu qu'apparemment, NSUserDefaults ne stocke que certains types d'informations. Alors, comment puis-je obtenir mes objets dans NSUSerDefaults?

J'ai lu qu'il devrait y avoir un moyen de "coder" mon objet personnalisé et de le mettre ensuite, mais je ne sais pas comment l'implémenter, une aide serait appréciée! Je vous remercie!

****MODIFIER****

Bon, alors j'ai travaillé avec le code donné ci-dessous (merci!), Mais j'ai toujours des problèmes. Fondamentalement, le code se bloque maintenant et je ne sais pas pourquoi, car il ne génère aucune erreur. Peut-être me manque quelque chose de base et je suis trop fatigué, mais nous verrons. Voici l'implémentation de ma classe personnalisée, "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

Fichier d'implémentation:

#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

Donc, c'est ma classe, assez simple, je sais que cela fonctionne dans la fabrication de mes objets. Voici donc les parties pertinentes du fichier AppDelegate (où j'appelle les fonctions de chiffrement et de déchiffrement):

@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

Et puis les parties importantes du fichier d'implémentation:

    #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, désolé pour tout le code. J'essaye juste d'aider. Fondamentalement, l'application se lancera puis se plantera immédiatement. Je l'ai réduit à la partie chiffrée de l'application, c'est là que ça se bloque, alors je fais quelque chose de mal, mais je ne sais pas quoi. L'aide serait appréciée à nouveau, merci!

(Je n'ai pas encore décodé, car le chiffrement ne fonctionne pas encore.)

265
Ethan Mick

Sur votre classe Player, implémentez les deux méthodes suivantes (en substituant les appels à encodeObject par quelque chose de pertinent pour votre propre objet):

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

Lecture et écriture 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;
}

Code emprunté sans vergogne à: classe de sauvegarde dans nsuserdefaults

511
chrissr

Je crée une bibliothèque RMMapper ( https://github.com/roomorama/RMMapper ) pour aider à enregistrer un objet personnalisé dans NSUserDefaults plus facilement et plus facilement, car implémenter encodeWithCoder et initWithCoder est super chiant!

Pour marquer une classe comme archivable, utilisez simplement: #import "NSObject+RMArchivable.h"

Pour enregistrer un objet personnalisé dans NSUserDefaults:

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

Pour obtenir un objet personnalisé à partir de NSUserDefaults:

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

Swift 4

Introduit le protocole Codable qui fait toute la magie pour ce genre de tâches. Conformez-vous simplement à votre structure/classe personnalisée:

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

Et pour stocker dans les paramètres par défaut, vous pouvez utiliser le 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)

Cela fonctionnera de la même manière pour les tableaux et autres classes de conteneur de tels objets:

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

Si quelqu'un cherche une version Swift:

1) Créez une classe personnalisée pour vos données

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) Pour sauvegarder les données, utilisez la fonction suivante:

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

3) pour récupérer:

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

Remarque: Je suis en train de sauvegarder et de récupérer un tableau des objets de classe personnalisés.

30
Ankit Goel

En prenant la réponse de @ chrissr et en l'accompagnant, ce code peut être implémenté dans une catégorie Nice sur NSUserDefaults pour enregistrer et récupérer des objets personnalisés:

@interface NSUserDefaults (NSUserDefaultsExtensions)

- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;

@end


@implementation NSUserDefaults (NSUserDefaultsExtensions)


- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [self setObject:encodedObject forKey:key];
    [self synchronize];

}

- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
    NSData *encodedObject = [self objectForKey:key];
    id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

@end

Usage:

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

Swift

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")
    }
    }

pour stocker et récupérer:

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
        }
7
Vyacheslav

Synchronisez les données/objets que vous avez enregistrés dans NSUserDefaults

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

J'espère que cela vous aidera. Merci

6