How to store custom objects in NSUserDefaults

IosObjective CIphoneEncodingNsuserdefaults

Ios Problem Overview


Alright, so I've been doing some poking around, and I realize my problem, but I don't know how to fix it. I have made a custom class to hold some data. I make objects for this class, and I need to them to last between sessions. Before I was putting all my information in NSUserDefaults, but this isn't working.

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

That is the error message I get when I put my custom class, "Player", in the NSUserDefaults. Now, I've read up that apparently NSUserDefaults only stores some types of information. So how an I get my objects into NSUSerDefaults?

I read that there should be a way to to "encode" my custom object and then put it in, but I'm not sure how to implement it, help would be appreciated! Thank you!

EDIT

Alright, so I worked with the code given below (Thank you!), but I'm still having some issues. Basically, the code crashes now and I'm not sure why, because it doesn't give any errors. Perhaps I'm missing something basic and I'm just too tired, but we'll see. Here is the implementation of my Custom class, "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

Implementation File:

#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

So that's my class, pretty straight forward, I know it works in making my objects. So here is the relevant parts of the AppDelegate file (where I call the encryption and decrypt functions):

@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

And then the important parts of the implementation file:

    #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, sorry about all the code. Just trying to help. Basically, the app will launch and then crash immediatly. I've narrowed it down to the encryption part of the app, that's where it crashes, so I'm doing something wrong but I'm not sure what. Help would be appreciated again, thank you!

(I haven't gotten around to decrypting yet, as I haven't gotten encrypting working yet.)

Ios Solutions


Solution 1 - Ios

On your Player class, implement the following two methods (substituting calls to encodeObject with something relevant to your own 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;
}

Reading and writing from 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 shamelessly borrowed from: saving class in nsuserdefaults

Solution 2 - Ios

Swift 4 introduced the Codable protocol which does all the magic for these kinds of tasks. Just conform your custom struct/class to it:

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

And for storing in the Defaults you can use the 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)

It will work like that for arrays and other container classes of such objects too:

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

Solution 3 - Ios

I create a library RMMapper (https://github.com/roomorama/RMMapper) to help save custom object into NSUserDefaults easier and more convenient, because implementing encodeWithCoder and initWithCoder is super boring!

To mark a class as archivable, just use: #import "NSObject+RMArchivable.h"

To save a custom object into NSUserDefaults:

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

To get custom obj from NSUserDefaults:

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

Solution 4 - Ios

If anybody is looking for a swift version:

  1. Create a custom class for your data

    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. To save data use following function:

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

  3. To retrieve:

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

Note: Here I am saving and retrieving an array of the custom class objects.

Solution 5 - Ios

Taking @chrissr's answer and running with it, this code can be implemented into a nice category on NSUserDefaults to save and retrieve custom objects:

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

Solution 6 - Ios

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

to store and retrieve:

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
        }

Solution 7 - Ios

Synchronize the data/object that you have saved into NSUserDefaults

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

Hope this will help you. Thanks

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEthan MickView Question on Stackoverflow
Solution 1 - IoschrissrView Answer on Stackoverflow
Solution 2 - IosSergiu TodirascuView Answer on Stackoverflow
Solution 3 - IosthomasdaoView Answer on Stackoverflow
Solution 4 - IosAnkit GoelView Answer on Stackoverflow
Solution 5 - IosCarlos PView Answer on Stackoverflow
Solution 6 - IosVyacheslavView Answer on Stackoverflow
Solution 7 - IosMuhammad Aamir AliView Answer on Stackoverflow