将自定义对象存储在 NSMutableArray 中的 NSUserDefault 中

我最近一直试图将我的 iPhone 应用程序的搜索结果存储在 NSUserDefault 集合中。我还使用它来成功地保存用户注册信息,但出于某种原因,试图存储我的自定义 Location 类的 NSMutableArray 总是返回为空。

在本文中,我尝试将 NSMutableArray 转换为 NSData 元素,但是得到了相同的结果(可以在 iPhone 上使用 NSUserDefault 保存整数数组吗?)

我所尝试的代码示例如下:

保存:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

或者

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

或者

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

负载:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

或者

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);

或者

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

在下面的建议之后,我也在我的对象中实现了 NSCoder (忽略 NSString 的临时过度使用) :

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


}
68474 次浏览

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.

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.

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.

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

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