使用NSSecureCoding协议进行对象编解码

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

稍后再加载它:

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

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

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

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

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

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

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

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

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

使用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协议的对象添加对它们的支持。

1 收藏 评论

关于作者:shinancao

(新浪微博:@午后的小甜点) 个人主页 · 我的文章

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部