A: 

See "Why NSUserDefaults failed to save NSMutableDictionary in iPhone SDK? " (http://stackoverflow.com/questions/471830/why-nsuserdefaults-failed-to-save-nsmutabledictionary-in-iphone-sdk)


If you want to (de)serialize custom objects, you have to provide the functions to (de)serialize the data (NSCoding protocol). The solution you refer to works with the int array because the array is not an object but a contiguous chunk of memory.

f3lix
I think your right I'll have a look into NSCoding (unless you have a recommended linky), the objects themselves are made up on NSStrings that is all so I suppose I could just write my own serialiser and put them all into a string array
tigermain
Ok so I have implemented NSCode see above, but still no joy using NSArchiver
tigermain
A: 

I think it looks like the problem with your code is not saving the results array. Its loading the data try using

lastResults = [prefs arrayForKey:@"lastResults"];

This will return the array specified by the key.

Gcoop
A: 

I would recommend against trying to store stuff like this in the defaults db.

SQLite is fairly easy to pick up and use. I have an episode in one of my screencasts (http://pragprog.com/screencasts/v-bdiphone) about a simple wrapper that I wrote (you can get the code without buying the SC).

It's much cleaner to store app data in app space.

All that said if it still makes sense to put this data into the defaults db, then see the post f3lix posted.

Bill Dudney
Thanks for the suggestion but I dont really want to have to start writing SQL queries (as I see in your example) just for an object with a few strings
tigermain
+17  A: 

For loading custom objects in an array, this is what I've used to grab the array:

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
 NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
 if (oldSavedArray != nil)
  objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
 else
  objectArray = [[NSMutableArray alloc] init];
}

You should check that the data returned from the user defaults is not nil, because I believe unarchiving from nil causes a crash.

Archiving is simple, using the following code:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

As f3lix pointed out, you need to make your custom object comply to the NSCoding protocol. Adding methods like the following should do the trick:

- (void)encodeWithCoder:(NSCoder *)coder;
{
    [coder encodeObject:label forKey:@"label"];
    [coder encodeInteger:numberID forKey:@"numberID"];
}

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [[CustomObject alloc] init];
    if (self != nil)
    {
     label = [coder decodeObjectForKey:@"label"];
     numberID = [coder decodeIntegerForKey:@"numberID"];
    } 
    return self;
}
Brad Larson
I've copied your code and my object which is implementing NSCoder into a prototype but despite the NSData that comes out of the UserDefaults has data the final array still has a count of 0. Any ideas? I will try and upload my prototype somewhere
tigermain
I've uploaded it to http://www.anthonymain.com/downloads/UserDefaultsDemo.zip - debugging it I find that it does the unarchive successfully (data is stored correctly from initWithCoder) but the target array has a count of -1 and then the final mutable array has a count of 0
tigermain
You were missing a "return self" at the end of your initWithCoder: method. Adding that seems to allow the unarchiving to take place.
Brad Larson
I cant believe it was something that simple! Stupid coding blindness!
tigermain
I had same problem, and it was solved thank you very much Brad
fyasar
+2  A: 

Hi,

I think you've gotten an error in your initWithCoder method, at least in the provided code you don't return the 'self' object.

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

I use

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

and

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

and it works perfect for me.

With regards,

Stefan

Stephan you are the life saver, look at here;That line saved my mind "if (self = [super init])"http://stackoverflow.com/questions/1933285/nskeyedunarchiver-memory-leak-problem/1933399#1933399
fyasar
A: 

I am off to making a tower defense game, and will have about 40-50 towers and about 20 enemies on screen. Those objects will have a few properties each, like hp, damage, speed etc.

What is the limit of the defaults db? I don't feel like writing SQL queries for simply saving the state of my game..

Maciej Swic
This should be a separate question. Thanks.
JoePasq