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.)

upvote
  flag
Do you have a stack trace or more information about the crash, such as which line number is causing the crash? I'm not immediately seeing anything wrong with the code, so a starting point would be helpful. – chrissr
upvote
  flag
In above example you have used encodeObject to store self.life which is an int. You should use encodeInt instead. – broot

7 Answers 11

up vote 479 down vote accepted

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

upvote
  flag
Edited my post above to reflect my changes. – Ethan Mick
upvote
  flag
@chrissr you have an error in NSUserDefaults defaults = [NSUserDefaults standardUserDefaults]; ... should be NSUserDefaults *defaults. – Maggie
upvote
  flag
Hey guys/girls, don't forget to add a [defaults synchronize] as the last line in the saveCustomObject method. – karim
upvote
  flag
@karim synchronize is automatically invoked at periodic intervals, use this method only if you cannot wait for the automatic synchronization! From the apple-doc developer.apple.com/library/mac/#documentation/Cocoa/Referen‌​ce/… – matzino
2 upvote
  flag
NSKeyedArchiver rocks... seems it even automatically descends into NSArray or NSDictionary objects and encodes any encodable custom objects within. – BadPirate
upvote
  flag
I can use NSKeyedArchiver without implementing NSCoding. So, why i must use NSCoding, for what reason? – Igor Khomenko
upvote
  flag
So I have to code those methods for every property I have on every custom class! Surely there's a simpler way to do this?! – theDuncs
2 upvote
  flag
I'm not being pedantic , just a genuine question , isnt it against apples guidelines to use synthesized setters ie self.property in init methods ? – Samhan Salahuddin
upvote
  flag
@eddardstark developer.apple.com/library/mac/documentation/Cocoa/Conceptu‌​al/… So yes, you shouldn't do it, but it's generally safe. I don't think I ever encountered a bug that was caused by an accessor method being called in init/dealloc and I've worked in several projects where it was done quite frequently. – Erik B
upvote
  flag
@Erik B :- It has to do with KVO i think. If theres no KVO involved its safe. – Samhan Salahuddin
upvote
  flag
would this work for CCSprites also or do they have to be handled differently? – oopology
2 upvote
  flag
@chrissr Please change NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object]; for NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];. Variables don't match. – GoRoS
upvote
  flag
Don't you have to implement NSCoding protocol? – Zinan Xing
upvote
  flag
If you use the CustomClass in another Target, don't forget these steps: //allinonescript.com/questions/27212360/… – Stone

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

upvote
  flag
Note people from the future that as of Swift 3, "synchronize" should not be called by you at all. – Jozemite Apps
upvote
  flag
@JozemiteApps why? or can you post a link with explanation for this topic? – code4latte
upvote
  flag
@code4latte I don't know if it was changed but Apple's documentation states that "The synchronize() method, which is automatically invoked at periodic intervals, keeps the in-memory cache in sync with a user’s defaults database." Before, it said that you should only call it IF you are sure you need the data saved instantly. I've read around a couple of time that the user should not call it anymore. – Jozemite Apps

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"]; 
upvote
  flag
Your library assumes that you have properties for everything you want to persist and that you want to persist everything you have properties for. If this is true, your library could certainly be helpful, but I think you will find that in many cases it isn't. – Erik B
upvote
  flag
It is useful to persist plain object only, I would say its usage is pretty similar to Gson in Java. What cases you are looking at? – thomasdao
7 upvote
  flag
That was really helpful, glad I kept reading on the answers ;D – Albara
upvote
  flag
what about when my custom object have in property other custom object ? – Shial
upvote
  flag
@Shial: if you make other custom object archivable, it will be saved too – thomasdao
upvote
  flag
@thomasdao thanks for your answear. But i recive some problems cus I'm using ReactiveCocoa. I have no idea from where he took didSubscribe property name. – Shial
upvote
  flag
unable to save an array of core data objects by this class. – Nishant
upvote
  flag
I can save my custom objects by this valuable class. – Arpit B Parekh
upvote
  flag
As I see, your library RMMapper adds categories to NSObject, so that every NSObject implements the Methods "encodeWithCoder" and "initWithCoder". And your copy category adds "copyWithZone" to every instance of NSObject. Be aware, that existing classes (also from Apple) who already implements these methods could get into trouble. You should not "override" existing methods in categories. Correct me if I'm wrong. – Lumpy
upvote
  flag
@Lumpy yes you are correct. I am aware of this may interfere with other classes. My solution is short and most useful for plain model. Some developers who want to archive their model to NSUserDefaults, but cannot do so easily, will look for a quick way to implement it, and this utility is just the quickest way to get things done. It's possible to refactor it to a static method, but developer will need to have to create their own encodeWithCoder and call this static method. I'm not sure which is better, and open for discussion. – thomasdao

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

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.

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
        }
upvote
  flag
Just a reminder: self is not mandatory before the variables in init and encode. – the4kman

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)

Not the answer you're looking for? Browse other questions tagged or ask your own question.