2015-03-24 109 views
8

我决定使用NSSecureCoding而不是NSCoding,但我无法使其工作。使用NSSecureCoding强制类型

我期望以下代码失败,因为我编码NSString但试图解码NSNumber。然而,该对象被初始化而没有抛出异常。

+ (BOOL)supportsSecureCoding 
{ 
    return YES; 
} 

- (instancetype)initWithCoder:(NSCoder *)coder 
{ 
    // prints '1' as expected 
    NSLog(@"%d", coder.requiresSecureCoding); 

    // unexpectedly prints 'foo' (expecting crash) 
    NSLog(@"%@", [coder decodeObjectOfClass:NSNumber.class forKey:@"bar"]); 

    return [super init]; 
} 

- (void)encodeWithCoder:(NSCoder *)coder 
{ 
    [coder encodeObject:@"foo" forKey:@"bar"]; 
} 

下面是我用从上面测试片断代码:

MyClass *object = [[MyClass alloc] init]; 

NSMutableData *const data = [[NSMutableData alloc] init]; 
NSKeyedArchiver *const archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 
archiver.requiresSecureCoding = YES; 

[archiver encodeObject:object forKey:@"root"]; 
[archiver finishEncoding]; 

NSKeyedUnarchiver *const unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 
unarchiver.requiresSecureCoding = YES; 

[unarchiver decodeObjectOfClass:MyClass.class forKey:@"root"]; 
[unarchiver finishDecoding]; 

我失去的东西完全明显,或者为什么解码期间抛出也不例外?

+0

您的NSLogs是否实际打印出值? – Chase 2015-03-24 22:40:29

+0

是的,他们打印上面的评论。 – 2015-03-25 01:50:33

+1

如果您调用'[super initWithCoder:]'而不是'[super init]',问题是否会持续存在? – 2015-03-26 22:29:43

回答

9

看看-[NSCoder decodeObjectOfClass:forKey:]的定义,是的,你的代码示例应该是都抛出了异常。该方法的描述如下:

解码密钥的对象,仅限于指定的类。

和探讨说:

如果编码器响应YESrequiresSecureCoding,那么一个异常将如果要解码的类不实现NSSecureCoding或不ACLASS的isKindOfClass:抛出。

NSKeyedUnarchiver实施此方法有两个不一致之处,与其优化有关。首先是decodeObjectOfClass:forKey:decodeObjectForKey:仅在遇到第一次时才对对象进行解码。

例如,在下面的代码断言传递因为foofoo2开始作为相同的对象并解码只是一次同时foo3开始作为一个单独的对象和作为单独解码的结果。

func encodeWithCoder(coder:NSCoder) { 
    let foo = NSSet(objects: 1, 2, 3) 
    coder.encodeObject(foo, forKey: "foo") 
    coder.encodeObject(foo, forKey: "foo2") 
    coder.encodeObject(NSSet(objects: 1, 2, 3), forKey: "foo3") 
} 

required init(coder: NSCoder) { 
    let foo = coder.decodeObjectOfClass(NSSet.self, forKey: "foo") 
    let foo2 = coder.decodeObjectOfClass(NSSet.self, forKey: "foo2") 
    let foo3 = coder.decodeObjectOfClass(NSSet.self, forKey: "foo3") 
    assert(foo === foo2) 
    assert(foo !== foo3) 
    super.init() 
} 

看来只有在对象被实际解码时才检查类。已批准类的列表将与对象请求的类进行比较。所以在我前面的例子,我可以改变类foo2是我想做的事情,代码仍然可以运行,返回NSSet

required init(coder: NSCoder) { 
    let foo = coder.decodeObjectOfClass(NSSet.self, forKey: "foo") 
    let foo2 = coder.decodeObjectOfClass(NSMutableDictionary.self, forKey: "foo2") 
    assert(foo === foo2) 
    super.init() 
}

第二个矛盾,直接关系到你的榜样,是某些对象类型永远不会被解码。 NSKeyedArchiver将其所有数据存储为二进制属性列表,该列表根据Apple's source code对字符串,数据,数字,日期,字典和数组类型进行本地支持。当NSKeyedArchiver遇到NSString,NSNumberNSData对象(但不是子类)时,而不是使用encodeWithObject:对其进行编码并保存有关如何对其进行解码的信息,它只是将值直接存储在PList中。然后当您拨打decodeObjectOfClass:withKey:时,它会看到已经存在的字符串,并立即返回而无需解码。没有解码意味着没有阶级检查。

无论这种行为是好还是坏都可以辩论。更少的检查意味着更快的代码,但行为确实与API文档不匹配。也就是说,如果不能保证返回类型,你可能想知道什么是安全编码。使用NSKeyedUnarchiver的安全编码可以保护您免受恶意制作的归档文件无法让您在任意类上调用alloc/initWithCoder:。如果您想要的更多,您可以创建一个验证所有decodeObjectOfClass:withKey:decodeObjectOfClasses:withKey:调用的输出类型的子类。

+0

感谢您的详细解释。太糟糕了苹果的实施与文档不一致。 – 2015-04-02 15:44:16