NSCoding是把数据存储在iOS和Mac OS上的一种极其简单和方便的方式,它把模型对象直接转变成一个文件,然后再把这个文件重新加载到内存里,并不需要任何文件解析和序列化的逻辑。如果要把对象保存到一个数据文件中(假设这个对象实现了NSCoding协议),那么你可以像下面这样做:
1 2 |
Foo *someFoo = [[Foo alloc] init]; [NSKeyedArchiver archiveRootObject:someFoo toFile:someFile]; |
稍后再加载它:
1 |
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile]; |
这样做对于编译进APP里的资源来说是可以的(例如nib文件,它在底层使用了NSCoding),但是使用NSCoding来读写用户数据文件的问题在于,把全部的类编码到一个文件里,也就间接地给了这个文件访问你APP里面实例类的权限。
虽然你不能在一个NSCoded文件里(至少在iOS中的)存储可执行代码,但是一名黑客可以使用特制地文件骗过你的APP进入到实例化类中,这是你从没打算做的,或者是你想要在另一个不同的上下文时才做的。尽管以这种方式造成实际性的破坏很难,但是无疑会导致用户的APP崩溃掉或者数据丢失。
在iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。
大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了,所以能安全地写有关归档的代码,你可以确保正在加载的数据文件是安全的。实现的方式如下:
1 2 3 4 5 6 7 |
// Set up NSKeyedUnarchiver to use secure coding NSData *data = [NSData dataWithContentsOfFile:someFile]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; [unarchiver setRequiresSecureCoding:YES]; // Decode object Foo *someFoo = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; |
注意一下,如果要让编写归档的代码是安全的,那么存储在文件中的每一个对象都要实现NSSecureCoding协议,否则会有异常抛出。如果要告诉框架自定义的类支持NSSecureCoding协议,那么你必须在initWithCoder: method方法中实现新的解码逻辑,并且supportsSecureCodin方法要返回YES。encodeWithCoder:方法没有变化,因为与安全相关的事是围绕加载进行的,而不是保存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
@interface Foo : NSObject @property (nonatomic, strong) NSNumber *property1; @property (nonatomic, copy) NSArray *property2; @property (nonatomic, copy) NSString *property3; @end @implementation Foo + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)coder { if ((self = [super init])) { // Decode the property values by key, specifying the expected class _property1 = [coder decodeObjectOfClass:[NSNumber class] forKey:@"property1"]; _property2 = [coder decodeObjectOfClass:[NSArray class] forKey:@"property2"]; _property3 = [coder decodeObjectOfClass:[NSString class] forKey:@"property3"]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { // Encode our ivars using string keys as normal [coder encodeObject:_property1 forKey:@"property1"]; |