使用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 工作正常,但是当我尝试从文件加载现有实例时,应用程序崩溃,因为它试图解码 intfloat

有没有好的方法来处理这个问题?理想情况下,我将能够加载现有的 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 字段。最好为此字段使用 intNSInteger

#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 数月或数年后使用它时它很容易使用)。