【问题标题】:Storing custom objects in an NSMutableArray in NSUserDefaults在 NSUserDefaults 的 NSMutableArray 中存储自定义对象
【发布时间】:2009-02-11 14:32:38
【问题描述】:

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

从这篇文章开始,我尝试将 NSMutableArray 转换为 NSData 元素,但得到了相同的结果 (Possible to save an integer array using NSUserDefaults on iPhone?)

我尝试过的代码示例是:

保存:

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

}

【问题讨论】:

    标签: cocoa-touch


    【解决方案1】:

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

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

    【讨论】:

    • 您在 initWithCoder: 方法的末尾缺少“返回自我”。添加它似乎可以进行取消归档。
    • 您不应该将数组存储到 nsuserdefaults 中,因为数组对于 nsuserdefaults 来说可能会变得太大。应该使用 + (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path 将其存储到文件中。为什么这个答案投票率这么高?
    • @mskw - 你说得对,NSUserDefaults 不应该用于存储大型数组(应该使用 Core Data 或原始 SQLite),但是存储小型数组非常好那里的编码对象。无论如何,问题是问如何做到这一点,这就是上面的代码提供的。至于选票,你的猜测和我的一样好。在过去的四年里,一定有很多人想这样做。
    • @Goodsum - 不,效果很好。试试看,它会返回一个确实是可变的 NSMutableArray。它只是通过向其中添加项目数组来启动新数组。
    • @AlbertRenshaw - 您添加到上面代码中的andSync 来自哪里?这不是 NSUserDefaults 的实际方法签名。此外,synchronize 不再像以前那样有用:twitter.com/Catfish_Man/status/647274106056904704
    【解决方案2】:

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

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

    它非常适合我。

    关于,

    斯蒂芬

    【讨论】:

    • 斯蒂芬你是救生员,看这里;那句话让我想起了“if (self = [super init])”stackoverflow.com/questions/1933285/…
    • 我得到了 ERROR_BAD_ACCESS 但你救了我。使用仅保留的属性并添加 self.variable 解决了它。
    【解决方案3】:

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

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

    这将返回键指定的数组。

    【讨论】:

      【解决方案4】:

      请参阅“为什么 NSUserDefaults 无法在 iPhone SDK 中保存 NSMutableDictionary? " (Why NSUserDefaults failed to save NSMutableDictionary in iPhone SDK?)


      如果你想(反)序列化自定义对象,你必须提供函数来(反)序列化数据(NSCoding 协议)。您提到的解决方案适用于 int 数组,因为该数组不是一个对象,而是一块连续的内存。

      【讨论】:

      • 我认为你的权利我会看看 NSCoding(除非你有推荐的链接),对象本身是由 NSStrings 组成的,所以我想我可以编写自己的序列化器并将它们全部放入一个字符串数组中
      • 好的,我已经实现了 NSCode 见上文,但使用 NSArchiver 仍然没有乐趣
      【解决方案5】:

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

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

      在应用空间中存储应用数据更加简洁。

      如果将这些数据放入默认数据库仍然有意义的话,那么请参阅 f3lix 发布的帖子。

      【讨论】:

      • 感谢您的建议,但我真的不想只为带有几个字符串的对象开始编写 SQL 查询(正如我在您的示例中看到的那样)
      猜你喜欢
      • 2011-02-28
      • 2011-05-10
      • 1970-01-01
      • 2017-08-30
      • 2011-01-19
      相关资源
      最近更新 更多