使用NSCoding时如何处理字段类型变化
How to deal with field type changes when using NSCoding
我有以下 class 实现 NSCoding
并且我已经创建了它的几个实例并将它们保存到文件中。
@interface BiscuitTin ()
@property NSString *biscuitType;
@property int numBiscuits;
@end
@implementation BiscuitTin
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.biscuitType = [coder decodeObjectForKey:@"biscuitType"];
self.numBiscuits = [coder decodeIntForKey:@"numBiscuits"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
[coder encodeObject:self.biscuitType forKey:@"biscuitType"];
[coder encodeInt:self.numBiscuits forKey:@"numBiscuits"];
}
@end
我现在决定,我希望将 numBiscuits
表示为 float
(因为可能有部分吃掉的饼干)。更新 属性 类型和 encodeWithCoder
工作正常,但是当我尝试从文件加载现有实例时,应用程序崩溃,因为它试图解码 int
和 float
。
有没有好的方法来处理这个问题?理想情况下,我将能够加载现有的 int
值并将其转换为 float
,但我不介意只使用默认值而不是崩溃。
我考虑过将适用的 decode
行包装在 try-catch 中,但在我的实际用例中,大约有 50 个左右的属性被 encoded/decoded 最好不要必须对每一个改变类型的对象进行显式处理。
在类似情况下,我更新了代码以使用新名称保存更改的 属性。然后 initWithCoder:
代码查找新名称。如果不存在,它会在旧名称下查找旧值。
因此您的代码的版本 2(属性 更改为 float
)将类似于以下内容:
@interface BiscuitTin ()
@property NSString *biscuitType;
@property float numBiscuits;
@end
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.biscuitType = [coder decodeObjectForKey:@"biscuitType"];
if ([coder containsValueForKey:@"numBiscuits2"]) {
// Process a version 2 archive
self.numBiscuits = [coder decodeFloatForKey:@"numBiscuits2"];
} else if ([coder containsValueForKey:@"numBiscuits"]) {
// Process a version 1 archive
int oldIntVal = [coder decodeIntForKey:@"numBiscuits"];
self.numBiscuits = oldIntVal;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
[coder encodeObject:self.biscuitType forKey:@"biscuitType"];
// [coder encodeInt:self.numBiscuits forKey:@"numBiscuits"]; // obsolete version
[coder encodeFloat:self.numBiscuits forKey:@"numBiscuits2"]; // new key name
}
当我自己处理这种情况时(我有过几次),我使用versioning。换句话说,将版本号与其余编码数据一起编码。
即使您从一开始就没有版本控制,那也不是什么大问题,您现在可以从版本 1
开始,只需将缺少 version
值视为等同于版本 0
.
如何?
在您正在编码的对象中存储一个 version
字段。最好为此字段使用 int
或 NSInteger
。
#define CURRENT_VERSION 1
#define MIN_VERSION_WITH_FEATURE_X 1
@implementation BiscuitTin
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
...
int vers = [coder decodeIntForKey:@"version"];
if (vers >= MIN_VERSION_WITH_FEATURE_X) {
// handle feature X. In your case, decoding a `float`
} else {
// handle prior version.
// In your case, decoding an `int` and converting to `float`
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
...
[coder encodeInt:CURRENT_VERSION forKey:@"version"];
}
@end
每当您添加一个不向后兼容的新功能时,递增 CURRENT_VERSION
,添加一个新的 MIN_VERSION_WITH_FEATURE_Y
常量和新的 CURRENT_VERSION
整数值,以及另一个分支-initWithCoder:
.
中的 if
语句
有点乱,但我想这是向后兼容的代价。 (从好的方面来说,我认为这种技术是相当自我记录的,因此当您 return 数月或数年后使用它时它很容易使用)。
我有以下 class 实现 NSCoding
并且我已经创建了它的几个实例并将它们保存到文件中。
@interface BiscuitTin ()
@property NSString *biscuitType;
@property int numBiscuits;
@end
@implementation BiscuitTin
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.biscuitType = [coder decodeObjectForKey:@"biscuitType"];
self.numBiscuits = [coder decodeIntForKey:@"numBiscuits"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
[coder encodeObject:self.biscuitType forKey:@"biscuitType"];
[coder encodeInt:self.numBiscuits forKey:@"numBiscuits"];
}
@end
我现在决定,我希望将 numBiscuits
表示为 float
(因为可能有部分吃掉的饼干)。更新 属性 类型和 encodeWithCoder
工作正常,但是当我尝试从文件加载现有实例时,应用程序崩溃,因为它试图解码 int
和 float
。
有没有好的方法来处理这个问题?理想情况下,我将能够加载现有的 int
值并将其转换为 float
,但我不介意只使用默认值而不是崩溃。
我考虑过将适用的 decode
行包装在 try-catch 中,但在我的实际用例中,大约有 50 个左右的属性被 encoded/decoded 最好不要必须对每一个改变类型的对象进行显式处理。
在类似情况下,我更新了代码以使用新名称保存更改的 属性。然后 initWithCoder:
代码查找新名称。如果不存在,它会在旧名称下查找旧值。
因此您的代码的版本 2(属性 更改为 float
)将类似于以下内容:
@interface BiscuitTin ()
@property NSString *biscuitType;
@property float numBiscuits;
@end
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.biscuitType = [coder decodeObjectForKey:@"biscuitType"];
if ([coder containsValueForKey:@"numBiscuits2"]) {
// Process a version 2 archive
self.numBiscuits = [coder decodeFloatForKey:@"numBiscuits2"];
} else if ([coder containsValueForKey:@"numBiscuits"]) {
// Process a version 1 archive
int oldIntVal = [coder decodeIntForKey:@"numBiscuits"];
self.numBiscuits = oldIntVal;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
[coder encodeObject:self.biscuitType forKey:@"biscuitType"];
// [coder encodeInt:self.numBiscuits forKey:@"numBiscuits"]; // obsolete version
[coder encodeFloat:self.numBiscuits forKey:@"numBiscuits2"]; // new key name
}
当我自己处理这种情况时(我有过几次),我使用versioning。换句话说,将版本号与其余编码数据一起编码。
即使您从一开始就没有版本控制,那也不是什么大问题,您现在可以从版本 1
开始,只需将缺少 version
值视为等同于版本 0
.
如何?
在您正在编码的对象中存储一个 version
字段。最好为此字段使用 int
或 NSInteger
。
#define CURRENT_VERSION 1
#define MIN_VERSION_WITH_FEATURE_X 1
@implementation BiscuitTin
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
...
int vers = [coder decodeIntForKey:@"version"];
if (vers >= MIN_VERSION_WITH_FEATURE_X) {
// handle feature X. In your case, decoding a `float`
} else {
// handle prior version.
// In your case, decoding an `int` and converting to `float`
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *coder) {
...
[coder encodeInt:CURRENT_VERSION forKey:@"version"];
}
@end
每当您添加一个不向后兼容的新功能时,递增 CURRENT_VERSION
,添加一个新的 MIN_VERSION_WITH_FEATURE_Y
常量和新的 CURRENT_VERSION
整数值,以及另一个分支-initWithCoder:
.
if
语句
有点乱,但我想这是向后兼容的代价。 (从好的方面来说,我认为这种技术是相当自我记录的,因此当您 return 数月或数年后使用它时它很容易使用)。