Reputation: 6068
I've recently been trying to store the search results of my iPhone app in the NSUserDefaults collection. I also use this to save user registration info successfully, but for some reason trying to store my NSMutableArray of custom Location classes always comes back empty.
I tried converting the NSMutableArray to a NSData element as of this post but I get the same result (Possible to save an integer array using NSUserDefaults on iPhone?)
The code samples I have tried are:
Save:
[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];
or
NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];
or
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];
Load:
lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];
or
NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);
or
NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];
After following advice below I have also implemented NSCoder in my object (ignore the overuse of NSString its temporary):
#import "Location.h"
@implementation Location
@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;
- (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"];
}
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:locationId forKey:@"locationId"];
[coder encodeObject:companyName forKey:@"companyName"];
[coder encodeObject:addressLine1 forKey:@"addressLine1"];
[coder encodeObject:addressLine2 forKey:@"addressLine2"];
[coder encodeObject:city forKey:@"city"];
[coder encodeObject:postcode forKey:@"postcode"];
[coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
[coder encodeObject:description forKey:@"description"];
[coder encodeObject:rating forKey:@"rating"];
[coder encodeObject:priceGuide forKey:@"priceGuide"];
[coder encodeObject:latitude forKey:@"latitude"];
[coder encodeObject:longitude forKey:@"longitude"];
[coder encodeObject:userLatitude forKey:@"userLatitude"];
[coder encodeObject:userLongitude forKey:@"userLongitude"];
[coder encodeObject:searchType forKey:@"searchType"];
[coder encodeObject:searchId forKey:@"searchId"];
[coder encodeObject:distance forKey:@"distance"];
[coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
[coder encodeObject:contentProviderId forKey:@"contentProviderId"];
}
Upvotes: 101
Views: 68576
Reputation: 170309
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 = [super init];
if (self != nil)
{
label = [[coder decodeObjectForKey:@"label"] retain];
numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
}
return self;
}
Upvotes: 176
Reputation:
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
Upvotes: 13
Reputation: 3358
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.
Upvotes: 0
Reputation: 29879
See "Why NSUserDefaults failed to save NSMutableDictionary in iPhone SDK? " (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.
Upvotes: 0
Reputation: 3382
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.
Upvotes: 0