在NSUserDefaults中的NSMutableArray中存储自定义对象


101

我最近一直在尝试将iPhone应用程序的搜索结果存储在NSUserDefaults集合中。我还使用它成功保存了用户注册信息,但是由于某种原因,尝试存储自定义Location类的NSMutableArray总是空着。

截至本文发布之时,我尝试将NSMutableArray转换为NSData元素,但得到的结果相同(可以在iPhone上使用NSUserDefaults保存整数数组吗?

我尝试过的代码示例是:

保存:

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

}

Answers:


177

为了在数组中加载自定义对象,这是我用来获取数组的方法:

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

您应该检查从用户默认值返回的数据是否不是nil,因为我认为从nil取消存档会导致崩溃。

归档很简单,使用以下代码:

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

正如f3lix指出的那样,您需要使您的自定义对象符合NSCoding协议。添加如下所示的方法应该可以解决问题:

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

3
您在initWithCoder:方法的末尾缺少“返回自己”。添加该内容似乎可以进行取消存档。
布拉德·拉尔森

2
您不应该将阵列存储到nsuserdefaults中,因为对于nsuserdefaults而言,阵列可能会变得太大。相反,应使用+(BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path将其存储到文件中。为什么这个答案这么多?
mskw 2012年

1
@mskw-您是正确的,不应将NSUserDefaults用于存储大型数组(应使用Core Data或Raw SQLite代替),但是在其中存放编码对象的小型数组是完全可以的。无论如何,问题都在问如何执行此操作,即上述代码提供的内容。就选票而言,您的猜测与我的一样。在过去的四年中,很多人肯定想这样做。
布拉德·拉尔森

2
@Goodsum-不,那很好。尝试一下,它将返回一个确实可变的NSMutableArray。它只是通过将项目数组添加到其中来启动新数组。
布拉德·拉尔森

1
@AlbertRenshaw- andSync您添加到上述代码中的来自哪里?这不是NSUserDefaults的实际方法签名。另外,synchronize它还没有从前有用:twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

我认为您的initWithCoder方法出错,至少在提供的代码中,您没有返回“自我”对象。

- (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


}

我用

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

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

它对我来说很完美。

带着敬意,

斯特凡


3
史蒂芬(Stephan)是救生员,请看这里;该行救了我的脑海:“如果(自= [超级初始化])” stackoverflow.com/questions/1933285/...
fyasar

我收到了ERROR_BAD_ACCESS,但您救了我。使用仅保留的属性并添加self.variable解决了它。
大卫,

0

我认为您的代码似乎没有保存结果数组的问题。其加载数据尝试使用

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

这将返回键指定的数组。


0

请参阅“为什么NSUserDefaults无法在iPhone SDK中保存NSMutableDictionary?”(为什么NSUserDefaults无法在iPhone SDK中保存NSMutableDictionary?


如果要对自定义对象进行(反序列化),则必须提供对数据进行(反序列化)功能(NSCoding协议)。您引用的解决方案适用于int数组,因为该数组不是对象,而是连续的内存块。


我想您是对的,我将看一下NSCoding(除非有推荐的链接),对象本身是由NSString组成的,所以我想我可以编写自己的序列化器并将它们全部放入字符串数组中
Anthony Main

好的,所以我已经实现了NSCode,见上面,但是使用NSArchiver仍然没有乐趣
Anthony Main

0

我建议不要尝试将此类内容存储在默认数据库中。

SQLite相当容易使用。在我的一个截屏视频(http://pragprog.com/screencasts/v-bdiphone)中有一集关于我编写的简单包装的一集(您无需购买SC即可获得代码)。

在应用程序空间中存储应用程序数据要干净得多。

所有这些表明,如果仍然可以将此数据放入默认数据库,请参阅f3lix发布。


1
感谢您的建议,但我真的不想只为带有几个字符串的对象开始编写SQL查询(如您在示例中所见)
Anthony Main
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.