NSSecureCoding协议进行对象编解码

转载自:http://blog.jobbole.com/67655/  只是留作个人学习,如果有问题,即可删除


NSCoding是把数据存储在iOS和Mac OS上的一种极其简单和方便的方式,它把模型对象直接转变成一个文件,然后再把这个文件重新加载到内存里,并不需要任何文件解析和序列化的逻辑。如果要把对象保存到一个数据文件中(假设这个对象实现了NSCoding协议),那么你可以像下面这样做:


Foo * someFoo = [ [ Foo alloc ] init ] ;
[ NSKeyedArchiver archiveRootObject : someFoo toFile : someFile ] ;
稍后再加载它:

Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];

这样做对于编译进APP里的资源来说是可以的(例如nib文件,它在底层使用了NSCoding),但是使用NSCoding来读写用户数据文件的问题在于,把全部的类编码到一个文件里,也就间接地给了这个文件访问你APP里面实例类的权限。

虽然你不能在一个NSCoded文件里(至少在iOS中的)存储可执行代码,但是一名黑客可以使用特制地文件骗过你的APP进入到实例化类中,这是你从没打算做的,或者是你想要在另一个不同的上下文时才做的。尽管以这种方式造成实际性的破坏很难,但是无疑会导致用户的APP崩溃掉或者数据丢失。

在iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。

// Set up NSKeyedUnarchiver to use secure coding
NSData * data = [ NSData dataWithContentsOfFile : someFile ] ;
NSKeyedUnarchiver * unarchiver = [ [ NSKeyedUnarchiver alloc ] initForReadingWithData : data ] ;
[ unarchiver setRequiresSecureCoding : YES ] ;
 
// Decode object

注意一下,如果要让编写归档的代码是安全的,那么存储在文件中的每一个对象都要实现NSSecureCoding协议,否则会有异常抛出。如果要告诉框架自定义的类支持NSSecureCoding协议,那么你必须在initWithCoder: method方法中实现新的解码逻辑,并且supportsSecureCodin方法要返回YES。encodeWithCoder:方法没有变化,因为与安全相关的事是围绕加载进行的,而不是保存:
Foo * someFoo = [ unarchiver decodeObjectForKey : NSKeyedArchiveRootObjectKey ] ;

大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了,所以能安全地写有关归档的代码,你可以确保正在加载的数据文件是安全的。实现的方式如下:

 @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"];


  [coder encodeObject:_property2 forKey:@"property2"];


  [coder encodeObject:_property3 forKey:@"property3"];


}


@end


几周前,我写了一篇关于如何自动实现NSCoding的文章,它利用反射机制确定运行时类的属性。


这是一种给所有的模型对象添加NSCoding支持的很好的方式,在initWithCoder:/encodeWithCoder: 方法中,你不再需要写重复的并且容易出错的代码了。但是我们使用的方法没有支持NSSecureCoding,因为我们不打算在对象被加载时校验其类型。

那么怎么改善这个自动NSCoding系统,使其以正确的方式支持NSSecureCoding呢?

回想一下,最开始的实现原理是利用class_copyPropertyList() 和 property_getName()这样两个运行时方法,产生属性名称列表,我们再把它们在数组中排序:

// Import the Objective-C runtime headers
 
#import <objc/runtime.h>
 
- ( NSArray * ) propertyNames
 
{     
 
   // Get the list of properties
 
   unsigned int propertyCount ;
 
   objc_property_t * properties = class_copyPropertyList ( [ self class ] ,
 
     &propertyCount ) ;
 
   NSMutableArray * array = [ NSMutableArray arrayWithCapacity : propertyCount ] ;
 
   for ( int i = 0 ; i < propertyCount ; i ++ )
 
   {
 
     // Get property name
 
     objc_property_t property = properties [ i ] ;
 
     const char * propertyName = property_getName ( property ) ;
 
     NSString * key = @ ( propertyName ) ;
 
     // Add to array
 
     [ array addObject : key ] ;
 
   }
 
   // Remember to free the list because ARC doesn't do that for us
 
   free ( properties ) ;
 
   return array ;
 
}

使用KVC(键-值编码),我们能够利用名称设置和获取一个对象的所有属性,并且在一个NSCoder对象中对这些属性进行编码/解码。

为了要实现NSSecureCoding,我们要遵循同样的原则,但是不仅仅是获取属性名,还需要获取它们的类型。幸运地是,Objective C运行时存储了类的属性类型的详细信息,所以可以很容易和名字一起取到这些数据。

一个类的属性可以是基本数据类型(例如整型、布尔类型和结构体),或者对象(例如字符串、数组等等)。KVC中的valueForKey: and setValue:forKey:方法实现了对基本类型的自动“装箱”,也就是说它们会把整型、布尔型和结构体各自转变成NSNumber和NSValue对象。这使事情变得简单了很多,因为我们只要处理装箱过的类型(对象)即可,所以我们可以声明属性类型为类,而不用为不同的属性类型调用不同的解码方法。

尽管运行时方法没有提供已装箱的类名,但是它们提供了类型编码—一种特殊格式化的C风格的字符串,它包含了类型信息(与@encode(var);返回的形式一样)。因为没有方法自动获取到基本类型对应的装箱过的类,所以我们需要解析这个字符串,然后指定其合适的类型。

类型编码字符串形式的文档说明在这里

第一个字母代表了基本类型。Objective C使用一个唯一的字母表示每一个支持的基本类型,例如’i’表示integer,’f’表示float,’d’表示double,等等。对象用’@’表示(紧跟着的是类名),还有其他一些不常见的类型,例如’:’表示selectors,’#’表示类。

结构体和联合体表示为大括号里面的表达式。只有几种类型是KVC机制所支持的,但是支持的那些类通常被装箱为NSValue对象,所以可用一种方式处理以’{’开头的任何值。

如果根据字符串的首字母来转换,那么我们可以处理所有已知的类型:

如果要处理’@’类型,则需要提去出类名。类名可能包括协议(实际上我们并不需要用到),所以划分字符串拿准确的类名,然后使用NSClassFromString得到类:

最后,把上面的解析过程和前面实现的propertyNames方法结合起来,创建一个方法返回属性类的字典,属性名称作为字典的键。下面是完成的实现过程:

最难的部分已经完成了。现在,要实现NSSecureCoding,只要将initWithCoder:方法中之前写的自动编码实现的部分,改为在解析时考虑到属性的类就可以了。此外,还需让supportsSecureCoding方法返回YES:

这样就得到了一个用于描述模型对象的简单的基类,并且它以正确的方式支持NSSecureCoding。此外,你可以使用我的AutoCoding扩展,它利用这种方法自动给没有实现NSCoding 和 NSSecureCoding协议的对象添加对它们的支持。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值