如何在Objective-C中将结构体保存到NSUserDefaults?

4

如何将自定义的struct保存到NSUserDefaults中?

struct Paging {
    NSInteger currentPage;
    NSInteger totalResults;
    NSInteger resultsPerPage;
};

typedef struct Paging Paging;

NSUserDefaults *userInfo = [NSUserDefaults standardUserDefaults];
[userInfo setStruct:paging forKey:@"paging"];
[userInfo synchronize];

上述代码会产生一个运行时警告:
*** -[NSUserDefaults setObject:forKey:]: 尝试插入非属性值...
3个回答

5
在您的特定情况下,可以使用NSData,因为成员只是int,但对于这个问题的未来读者,请在执行此操作之前阅读Apple文档中关于编码和解码C数据类型 - 结构和位域 编码和解码C数据类型-结构和位域:

不应该使用NSData对象包装一个结构并存档它。如果该结构包含对象或指针字段,则数据对象将无法正确存档它们。还会创建一种依赖于编译器如何决定布局结构的方式,这可能会在编译器的版本之间发生变化,并且可能取决于其他因素。例如,在结构体字段之间可能有任意数量的内部和不可见填充字节,而这些量可能会在不同平台上以及没有通知的情况下发生变化。此外,任何多字节宽度的字段都将无法正确处理与字节顺序问题。您将给自己带来各种兼容性问题。

该文档确实说明了如何正确执行此操作(但是不幸的是没有给出示例-但是,当我想到一个时,我将使用示例更新此答案):

存档结构或位字段的最佳技术是独立存档各个字段,并为每个选择适当的编码/解码方法。如果愿意,可以从结构字段名称组成关键字,例如"theStruct.order""theStruct.flags"等。这会在一定程度上依赖于源代码中字段的名称,随着时间的推移,这些名称可能会被重命名,但是如果要保持兼容性,则归档密钥不能更改。


非常类似于NSData包装但使用NSValue的方法(这也是不鼓励的,但对于基本结构也有效):
struct Paging {
    NSInteger currentPage;
    NSInteger totalResults;
    NSInteger resultsPerPage;
};

typedef struct Paging Paging;

- (void)someMeth {
    // Here `paging_struct` is a struct of type `Paging`:
    NSValue *pagingStructValue;
    // On its own line just for readability
    pagingStructValue = [NSValue valueWithBytes:&paging_struct
                                       objCType:@encode(Paging)];

    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults setObject:pagingStructValue
                     forKey:@"paging"];
    [userDefaults synchronize];
}

NSValue Documentation


3
很遗憾,这种方法在 iOS 6 及以上版本对我无效。我收到了以下错误信息:尝试插入类为“NSConcreteValue”的非属性值……请注意,在属性列表中的字典和数组也必须仅包含属性值。 - Ben H

0

我在我的iOS应用程序中做出了不太理想的决定,使用了struct。现在我也面临这样一个问题,即我无法轻松地将包含约30个变量的结构体保存到NSUserDefaults中。

在尝试了SO和其他网站提供的许多解决方案后,对我来说都没有很好地工作。因此,最终我决定创建一个简单的方法将结构体转换为NSMutableDictionary。

-(NSMutableDictionary *)structToDictionary:(StructPictures) dat {


    NSArray *values = [NSArray arrayWithObjects:
                       [NSNumber numberWithFloat:dat.bgOpacity],
                       [NSNumber numberWithInt:dat.bgType], // 1= general,
                       [NSNumber numberWithFloat:dat.glowAmt],
                       [NSNumber numberWithInt:dat.textureNum],
                       [NSNumber numberWithInt:dat.patternNum],
                       [NSNumber numberWithInt:dat.patternType],
                       [NSNumber numberWithFloat:dat.patternHue],
                       [NSNumber numberWithFloat:dat.textureSizer],
                       [NSNumber numberWithInt:dat.stickerR],
                       [NSNumber numberWithInt:dat.stickerG],
                       [NSNumber numberWithInt:dat.stickerB]

                       , nil];


    NSArray *keys = [NSArray arrayWithObjects:
                     @"bgOpacity",
                     @"bgType",
                     @"glowAmt",
                     @"textureNum",
                     @"patternNum",
                     @"patternType",
                     @"patternHue",
                     @"textureSizer",
                     @"stickerR",
                     @"stickerG",
                     @"stickerB",
                     nil];

    NSMutableDictionary *res = [[NSMutableDictionary alloc] initWithObjects:values forKeys:keys];

return res;
}

StructPictures是我的结构体(自定义)的名称。 希望这能提供一种将结构体保存到NSUserDefaults的替代解决方案。


-2
一种方法是将其转换为NSData。
NSData* d = [NSData dataWithBytes: paging length: sizeof(paging)];

在这种情况下,您的结构非常简单,因此您可能希望添加一个类别到NSUserDefaults中以保存和恢复它。我会通过保存一个包含四个NSNumbers的NSDictionary来实现它。虽然需要更多的工作,但这意味着您的plist将是可读的。

1
这取决于struct的内容,可能存在潜在的危险。有关详细信息,请参阅chown的答案。在32位和64位架构之间,结构体的布局很可能是不同的。您可以添加__attribute__((packed))编译器指令来强制编译器删除任何填充,否则它会添加以使字段与本机架构对齐。 - Martijn Thé
真的,虽然我不认为给结构体填充会更安全。 - David Dunham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接