in case of'D':constkey_type

使用NSSecureCoding协议进行对象编解码 - IOS - 伯乐在线
& 使用NSSecureCoding协议进行对象编解码
NSCoding是把数据存储在iOS和Mac OS上的一种极其简单和方便的方式,它把模型对象直接转变成一个文件,然后再把这个文件重新加载到内存里,并不需要任何文件解析和序列化的逻辑。如果要把对象保存到一个数据文件中(假设这个对象实现了NSCoding协议),那么你可以像下面这样做:
Objective-C
Foo *someFoo = [[Foo alloc] init];
[NSKeyedArchiver archiveRootObject:someFoo toFile:someFile];
Foo *someFoo = [[Foo alloc] init];[NSKeyedArchiver archiveRootObject:someFoo toFile:someFile];
稍后再加载它:
Objective-C
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];
这样做对于编译进APP里的资源来说是可以的(例如nib文件,它在底层使用了NSCoding),但是使用NSCoding来读写用户数据文件的问题在于,把全部的类编码到一个文件里,也就间接地给了这个文件访问你APP里面实例类的权限。
虽然你不能在一个NSCoded文件里(至少在iOS中的)存储可执行代码,但是一名黑客可以使用特制地文件骗过你的APP进入到实例化类中,这是你从没打算做的,或者是你想要在另一个不同的上下文时才做的。尽管以这种方式造成实际性的破坏很难,但是无疑会导致用户的APP崩溃掉或者数据丢失。
在iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。
大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了,所以能安全地写有关归档的代码,你可以确保正在加载的数据文件是安全的。实现的方式如下:
Objective-C
// 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];
// Set up NSKeyedUnarchiver to use secure codingNSData *data = [NSData dataWithContentsOfFile:someFile];NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];[unarchiver setRequiresSecureCoding:YES];&// Decode objectFoo *someFoo = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
注意一下,如果要让编写归档的代码是安全的,那么存储在文件中的每一个对象都要实现NSSecureCoding协议,否则会有异常抛出。如果要告诉框架自定义的类支持NSSecureCoding协议,那么你必须在initWithCoder: method方法中实现新的解码逻辑,并且supportsSecureCodin方法要返回YES。encodeWithCoder:方法没有变化,因为与安全相关的事是围绕加载进行的,而不是保存:
Objective-C
@interface Foo : NSObject
@property (nonatomic, strong) NSNumber *property1;
@property (nonatomic, copy) NSArray *property2;
@property (nonatomic, copy) NSString *property3;
@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&];
- (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&];
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
@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支持的很好的方式,在initWithCoder:/encodeWithCoder: 方法中,你不再需要写重复的并且容易出错的代码了。但是我们使用的方法没有支持NSSecureCoding,因为我们不打算在对象被加载时校验其类型。
那么怎么改善这个自动NSCoding系统,使其以正确的方式支持NSSecureCoding呢?
回想一下,最开始的实现原理是利用class_copyPropertyList() 和 property_getName()这样两个运行时方法,产生属性名称列表,我们再把它们在数组中排序:
Objective-C
// Import the Objective-C runtime headers
#import &objc/runtime.h&
- (NSArray *)propertyNames
// Get the list of properties
unsigned int propertyC
objc_property_t *properties = class_copyPropertyList([self class],
&propertyCount);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:propertyCount];
for (int i = 0; i & propertyC 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);
12345678910111213141516171819202122232425262728293031323334353637383940414243
// 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对象,所以可用一种方式处理以’{’开头的任何值。
如果根据字符串的首字母来转换,那么我们可以处理所有已知的类型:
Objective-C
Class propertyClass =
char *typeEncoding = property_copyAttributeValue(property, &T&);
switch (typeEncoding[0])
case 'c': // Numeric types
case 'i':
case 's':
case 'l':
case 'q':
case 'C':
case 'I':
case 'S':
case 'L':
case 'Q':
case 'f':
case 'd':
case 'B':
propertyClass = [NSNumber class];
case '*': // C-String
propertyClass = [NSString class];
case '@': // Object
//TODO: get class name
case '{': // Struct
propertyClass = [NSValue class];
case '[': // C-Array
case '(': // Enum
case '#': // Class
case ':': // Selector
case '^': // Pointer
case 'b': // Bitfield
case '?': // Unknown type
propertyClass = // Not supported by KVC
free(typeEncoding);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
Class propertyClass = nil;&char *typeEncoding = property_copyAttributeValue(property, "T");&switch (typeEncoding[0])&{&&&case 'c': // Numeric types&&&case 'i':&&&case 's':&&&case 'l':&&&case 'q':&&&case 'C':&&&case 'I':&&&case 'S':&&&case 'L':&&&case 'Q':&&&case 'f':&&&case 'd':&&&case 'B':&&&{&&&&&propertyClass = [NSNumber class];&&&&&break;&&&}&&&case '*': // C-String&&&{&&&&&propertyClass = [NSString class];&&&&&break;&&&}&&&case '@': // Object&&&{&&&&&//TODO: get class name&&&&&break;&&&}&&&case '{': // Struct&&&{&&&&&propertyClass = [NSValue class];&&&&&break;&&&}&&&case '[': // C-Array&&&case '(': // Enum&&&case '#': // Class&&&case ':': // Selector&&&case '^': // Pointer&&&case 'b': // Bitfield&&&case '?': // Unknown type&&&default:&&&{&&&&&propertyClass = nil; // Not supported by KVC&&&&&break;&&&}&}&free(typeEncoding);
如果要处理’@’类型,则需要提去出类名。类名可能包括协议(实际上我们并不需要用到),所以划分字符串拿准确的类名,然后使用NSClassFromString得到类:
Objective-C
case '@':
// The objcType for classes will always be at least 3 characters long
if (strlen(typeEncoding) &= 3)
// Copy the class name as a C-String
char *cName = strndup(typeEncoding + 2, strlen(typeEncoding) - 3);
// Convert to an NSString for easier manipulation
NSString *name = @(cName);
// Strip out and protocols from the end of the class name
NSRange range = [name rangeOfString:@&&&];
if (range.location != NSNotFound)
name = [name substringToIndex:range.location];
// Get class from name, or default to NSObject if no name is found
propertyClass = NSClassFromString(name) ?: [NSObject class];
free(cName);
12345678910111213141516171819202122232425262728293031323334353637383940
case '@':&{&&&// The objcType for classes will always be at least 3 characters long&&&if (strlen(typeEncoding) >= 3)&&&{&&&&&// Copy the class name as a C-String&&&&&char *cName = strndup(typeEncoding + 2, strlen(typeEncoding) - 3);&&&&&// Convert to an NSString for easier manipulation&&&&&NSString *name = @(cName);&&&&&// Strip out and protocols from the end of the class name&&&&&NSRange range = [name rangeOfString:@"<"];&&&&&if (range.location != NSNotFound)&&&&&{&&&&&&&name = [name substringToIndex:range.location];&&&&&}&&&&&// Get class from name, or default to NSObject if no name is found&&&&&propertyClass = NSClassFromString(name) ?: [NSObject class];&&&&&free(cName);&&&}&&&break;}
最后,把上面的解析过程和前面实现的propertyNames方法结合起来,创建一个方法返回属性类的字典,属性名称作为字典的键。下面是完成的实现过程:
Objective-C
- (NSDictionary *)propertyClassesByName
// Check for a cached value (we use _cmd as the cache key,
// which represents @selector(propertyNames))
NSMutableDictionary *dictionary = objc_getAssociatedObject([self class], _cmd);
if (dictionary)
// Loop through our superclasses until we hit NSObject
dictionary = [NSMutableDictionary dictionary];
Class subclass = [self class];
while (subclass != [NSObject class])
unsigned int propertyC
objc_property_t *properties = class_copyPropertyList(subclass,
&propertyCount);
for (int i = 0; i & propertyC i++)
// Get property name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *key = @(propertyName);
// Check if there is a backing ivar
char *ivar = property_copyAttributeValue(property, "V");
// Check if ivar has KVC-compliant name
NSString *ivarName = @(ivar);
if ([ivarName isEqualToString:key] ||
[ivarName isEqualToString:[@"_" stringByAppendingString:key]])
// Get type
Class propertyClass =
char *typeEncoding = property_copyAttributeValue(property, "T");
switch (typeEncoding[0])
case 'c': // Numeric types
propertyClass = [NSNumber class];
case '*': // C-String
propertyClass = [NSString class];
case '@': // Object
//TODO: get class name
case '{': // Struct
propertyClass = [NSValue class];
case '[': // C-Array
case '(': // Enum
case '#': // Class
case ':': // Selector
case '^': // Pointer
case 'b': // Bitfield
case '?': // Unknown type
propertyClass = // Not supported by KVC
free(typeEncoding);
// If known type, add to dictionary
if (propertyClass) dictionary[propertyName] = propertyC
free(ivar);
free(properties);
subclass = [subclass superclass];
// Cache and return dictionary
objc_setAssociatedObject([self class], _cmd, dictionary,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
- (NSDictionary *)propertyClassesByName&{&&&// Check for a cached value (we use _cmd as the cache key, &&&// which represents @selector(propertyNames))&&&NSMutableDictionary *dictionary = objc_getAssociatedObject([self class], _cmd);&&&if (dictionary)&&&{&&&&&&&return dictionary;&&&}&&&// Loop through our superclasses until we hit NSObject&&&dictionary = [NSMutableDictionary dictionary];&&&Class subclass = [self class];&&&while (subclass != [NSObject class])&&&{&&&&&unsigned int propertyCount;&&&&&objc_property_t *properties = class_copyPropertyList(subclass, &&&&&&&&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);&&&&&&&// Check if there is a backing ivar&&&&&&&char *ivar = property_copyAttributeValue(property, "V");&&&&&&&if (ivar)&&&&&&&{&&&&&&&&&// Check if ivar has KVC-compliant name&&&&&&&&&NSString *ivarName = @(ivar);&&&&&&&&&if ([ivarName isEqualToString:key] || &&&&&&&&&&&[ivarName isEqualToString:[@"_" stringByAppendingString:key]])&&&&&&&&&{&&&&&&&&&&&// Get type&&&&&&&&&&&Class propertyClass = nil;&&&&&&&&&&&char *typeEncoding = property_copyAttributeValue(property, "T");&&&&&&&&&&&switch (typeEncoding[0])&&&&&&&&&&&{&&&&&&&&&&&&&case 'c': // Numeric types&&&&&&&&&&&&&case 'i':&&&&&&&&&&&&&case 's':&&&&&&&&&&&&&case 'l':&&&&&&&&&&&&&case 'q':&&&&&&&&&&&&&case 'C':&&&&&&&&&&&&&case 'I':&&&&&&&&&&&&&case 'S':&&&&&&&&&&&&&case 'L':&&&&&&&&&&&&&case 'Q':&&&&&&&&&&&&&case 'f':&&&&&&&&&&&&&case 'd':&&&&&&&&&&&&&case 'B':&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSNumber class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '*': // C-String&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSString class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '@': // Object&&&&&&&&&&&&&{&&&&&&&&&&&&&&&//TODO: get class name&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '{': // Struct&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSValue class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '[': // C-Array&&&&&&&&&&&&&case '(': // Enum&&&&&&&&&&&&&case '#': // Class&&&&&&&&&&&&&case ':': // Selector&&&&&&&&&&&&&case '^': // Pointer&&&&&&&&&&&&&case 'b': // Bitfield&&&&&&&&&&&&&case '?': // Unknown type&&&&&&&&&&&&&default:&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = nil; // Not supported by KVC&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&}&&&&&&&&&&&free(typeEncoding);&&&&&&&&&&&// If known type, add to dictionary&&&&&&&&&&&if (propertyClass) dictionary[propertyName] = propertyClass;&&&&&&&&&}&&&&&&&&&free(ivar);&&&&&&&}&&&&&}&&&&&free(properties);&&&&&subclass = [subclass superclass];&&&}&&&// Cache and return dictionary&&&objc_setAssociatedObject([self class], _cmd, dictionary, &&&&&OBJC_ASSOCIATION_RETAIN_NONATOMIC);&&&return dictionary;&}
最难的部分已经完成了。现在,要实现NSSecureCoding,只要将initWithCoder:方法中之前写的自动编码实现的部分,改为在解析时考虑到属性的类就可以了。此外,还需让supportsSecureCoding方法返回YES:
Objective-C
+ (BOOL)supportsSecureCoding
return YES;
- (id)initWithCoder:(NSCoder *)coder
if ((self = [super init]))
// Decode the property values by key, specifying the expected class
[[self propertyClassesByName] enumerateKeysAndObjectsUsingBlock:(void (^)(NSString *key, Class propertyClass, BOOL *stop)) {
id object = [aDecoder decodeObjectOfClass:propertyClass forKey:key];
if (object) [self setValue:object forKey:key];
- (void)encodeWithCoder:(NSCoder *)aCoder
for (NSString *key in [self propertyClassesByName])
id object = [self valueForKey:key];
if (object) [aCoder encodeObject:object forKey:key];
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
+ (BOOL)supportsSecureCoding&{&&&return YES;&}&- (id)initWithCoder:(NSCoder *)coder&{&&&if ((self = [super init]))&&&{&&&&&// Decode the property values by key, specifying the expected class&&&&&[[self propertyClassesByName] enumerateKeysAndObjectsUsingBlock:(void (^)(NSString *key, Class propertyClass, BOOL *stop)) {&&&&&&&id object = [aDecoder decodeObjectOfClass:propertyClass forKey:key];&&&&&&&if (object) [self setValue:object forKey:key];&&&&&}];&&&}&&&return self;&}&- (void)encodeWithCoder:(NSCoder *)aCoder&{&&&for (NSString *key in [self propertyClassesByName])&&&{&&&&&id object = [self valueForKey:key];&&&&&if (object) [aCoder encodeObject:object forKey:key];&&&}&}
这样就得到了一个用于描述模型对象的简单的基类,并且它以正确的方式支持NSSecureCoding。此外,你可以使用我的扩展,它利用这种方法自动给没有实现NSCoding 和 NSSecureCoding协议的对象添加对它们的支持。
关于作者:
可能感兴趣的话题
关于iOS频道
iOS频道分享iOS和Swift开发,应用设计和推广,iOS相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线使用NSSecureCoding协议进行对象编解码 - 文章 - 伯乐在线
& 使用NSSecureCoding协议进行对象编解码
NSCoding是把数据存储在iOS和Mac OS上的一种极其简单和方便的方式,它把模型对象直接转变成一个文件,然后再把这个文件重新加载到内存里,并不需要任何文件解析和序列化的逻辑。如果要把对象保存到一个数据文件中(假设这个对象实现了NSCoding协议),那么你可以像下面这样做:
Foo *someFoo = [[Foo alloc] init];
[NSKeyedArchiver archiveRootObject:someFoo toFile:someFile];
Foo *someFoo = [[Foo alloc] init];[NSKeyedArchiver archiveRootObject:someFoo toFile:someFile];
稍后再加载它:
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];
这样做对于编译进APP里的资源来说是可以的(例如nib文件,它在底层使用了NSCoding),但是使用NSCoding来读写用户数据文件的问题在于,把全部的类编码到一个文件里,也就间接地给了这个文件访问你APP里面实例类的权限。
虽然你不能在一个NSCoded文件里(至少在iOS中的)存储可执行代码,但是一名黑客可以使用特制地文件骗过你的APP进入到实例化类中,这是你从没打算做的,或者是你想要在另一个不同的上下文时才做的。尽管以这种方式造成实际性的破坏很难,但是无疑会导致用户的APP崩溃掉或者数据丢失。
在iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。
大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了,所以能安全地写有关归档的代码,你可以确保正在加载的数据文件是安全的。实现的方式如下:
// 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];
// Set up NSKeyedUnarchiver to use secure codingNSData *data = [NSData dataWithContentsOfFile:someFile];NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];[unarchiver setRequiresSecureCoding:YES];&// Decode objectFoo *someFoo = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
注意一下,如果要让编写归档的代码是安全的,那么存储在文件中的每一个对象都要实现NSSecureCoding协议,否则会有异常抛出。如果要告诉框架自定义的类支持NSSecureCoding协议,那么你必须在initWithCoder: method方法中实现新的解码逻辑,并且supportsSecureCodin方法要返回YES。encodeWithCoder:方法没有变化,因为与安全相关的事是围绕加载进行的,而不是保存:
@interface Foo : NSObject
@property (nonatomic, strong) NSNumber *property1;
@property (nonatomic, copy) NSArray *property2;
@property (nonatomic, copy) NSString *property3;
@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"];
- (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"];
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
@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支持的很好的方式,在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 propertyC
objc_property_t *properties = class_copyPropertyList([self class],
&propertyCount);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:propertyCount];
for (int i = 0; i & propertyC 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);
12345678910111213141516171819202122232425262728293031323334353637383940414243
// 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对象,所以可用一种方式处理以’{’开头的任何值。
如果根据字符串的首字母来转换,那么我们可以处理所有已知的类型:
Class propertyClass =
char *typeEncoding = property_copyAttributeValue(property, "T");
switch (typeEncoding[0])
case 'c': // Numeric types
propertyClass = [NSNumber class];
case '*': // C-String
propertyClass = [NSString class];
case '@': // Object
//TODO: get class name
case '{': // Struct
propertyClass = [NSValue class];
case '[': // C-Array
case '(': // Enum
case '#': // Class
case ':': // Selector
case '^': // Pointer
case 'b': // Bitfield
case '?': // Unknown type
propertyClass = // Not supported by KVC
free(typeEncoding);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
Class propertyClass = nil;&char *typeEncoding = property_copyAttributeValue(property, "T");&switch (typeEncoding[0])&{&&&case 'c': // Numeric types&&&case 'i':&&&case 's':&&&case 'l':&&&case 'q':&&&case 'C':&&&case 'I':&&&case 'S':&&&case 'L':&&&case 'Q':&&&case 'f':&&&case 'd':&&&case 'B':&&&{&&&&&propertyClass = [NSNumber class];&&&&&break;&&&}&&&case '*': // C-String&&&{&&&&&propertyClass = [NSString class];&&&&&break;&&&}&&&case '@': // Object&&&{&&&&&//TODO: get class name&&&&&break;&&&}&&&case '{': // Struct&&&{&&&&&propertyClass = [NSValue class];&&&&&break;&&&}&&&case '[': // C-Array&&&case '(': // Enum&&&case '#': // Class&&&case ':': // Selector&&&case '^': // Pointer&&&case 'b': // Bitfield&&&case '?': // Unknown type&&&default:&&&{&&&&&propertyClass = nil; // Not supported by KVC&&&&&break;&&&}&}&free(typeEncoding);
如果要处理’@’类型,则需要提去出类名。类名可能包括协议(实际上我们并不需要用到),所以划分字符串拿准确的类名,然后使用NSClassFromString得到类:
// The objcType for classes will always be at least 3 characters long
if (strlen(typeEncoding) &= 3)
// Copy the class name as a C-String
char *cName = strndup(typeEncoding + 2, strlen(typeEncoding) - 3);
// Convert to an NSString for easier manipulation
NSString *name = @(cName);
// Strip out and protocols from the end of the class name
NSRange range = [name rangeOfString:@"&"];
if (range.location != NSNotFound)
name = [name substringToIndex:range.location];
// Get class from name, or default to NSObject if no name is found
propertyClass = NSClassFromString(name) ?: [NSObject class];
free(cName);
12345678910111213141516171819202122232425262728293031323334353637383940
case '@':&{&&&// The objcType for classes will always be at least 3 characters long&&&if (strlen(typeEncoding) &= 3)&&&{&&&&&// Copy the class name as a C-String&&&&&char *cName = strndup(typeEncoding + 2, strlen(typeEncoding) - 3);&&&&&// Convert to an NSString for easier manipulation&&&&&NSString *name = @(cName);&&&&&// Strip out and protocols from the end of the class name&&&&&NSRange range = [name rangeOfString:@"&"];&&&&&if (range.location != NSNotFound)&&&&&{&&&&&&&name = [name substringToIndex:range.location];&&&&&}&&&&&// Get class from name, or default to NSObject if no name is found&&&&&propertyClass = NSClassFromString(name) ?: [NSObject class];&&&&&free(cName);&&&}&&&break;}
最后,把上面的解析过程和前面实现的propertyNames方法结合起来,创建一个方法返回属性类的字典,属性名称作为字典的键。下面是完成的实现过程:
- (NSDictionary *)propertyClassesByName
// Check for a cached value (we use _cmd as the cache key,
// which represents @selector(propertyNames))
NSMutableDictionary *dictionary = objc_getAssociatedObject([self class], _cmd);
if (dictionary)
// Loop through our superclasses until we hit NSObject
dictionary = [NSMutableDictionary dictionary];
Class subclass = [self class];
while (subclass != [NSObject class])
unsigned int propertyC
objc_property_t *properties = class_copyPropertyList(subclass,
&propertyCount);
for (int i = 0; i & propertyC i++)
// Get property name
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);
NSString *key = @(propertyName);
// Check if there is a backing ivar
char *ivar = property_copyAttributeValue(property, &V&);
// Check if ivar has KVC-compliant name
NSString *ivarName = @(ivar);
if ([ivarName isEqualToString:key] ||
[ivarName isEqualToString:[@&_& stringByAppendingString:key]])
// Get type
Class propertyClass =
char *typeEncoding = property_copyAttributeValue(property, &T&);
switch (typeEncoding[0])
case 'c': // Numeric types
case 'i':
case 's':
case 'l':
case 'q':
case 'C':
case 'I':
case 'S':
case 'L':
case 'Q':
case 'f':
case 'd':
case 'B':
propertyClass = [NSNumber class];
case '*': // C-String
propertyClass = [NSString class];
case '@': // Object
//TODO: get class name
case '{': // Struct
propertyClass = [NSValue class];
case '[': // C-Array
case '(': // Enum
case '#': // Class
case ':': // Selector
case '^': // Pointer
case 'b': // Bitfield
case '?': // Unknown type
propertyClass = // Not supported by KVC
free(typeEncoding);
// If known type, add to dictionary
if (propertyClass) dictionary[propertyName] = propertyC
free(ivar);
free(properties);
subclass = [subclass superclass];
// Cache and return dictionary
objc_setAssociatedObject([self class], _cmd, dictionary,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
- (NSDictionary *)propertyClassesByName&{&&&// Check for a cached value (we use _cmd as the cache key, &&&// which represents @selector(propertyNames))&&&NSMutableDictionary *dictionary = objc_getAssociatedObject([self class], _cmd);&&&if (dictionary)&&&{&&&&&&&return dictionary;&&&}&&&// Loop through our superclasses until we hit NSObject&&&dictionary = [NSMutableDictionary dictionary];&&&Class subclass = [self class];&&&while (subclass != [NSObject class])&&&{&&&&&unsigned int propertyCount;&&&&&objc_property_t *properties = class_copyPropertyList(subclass, &&&&&&&&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);&&&&&&&// Check if there is a backing ivar&&&&&&&char *ivar = property_copyAttributeValue(property, "V");&&&&&&&if (ivar)&&&&&&&{&&&&&&&&&// Check if ivar has KVC-compliant name&&&&&&&&&NSString *ivarName = @(ivar);&&&&&&&&&if ([ivarName isEqualToString:key] || &&&&&&&&&&&[ivarName isEqualToString:[@"_" stringByAppendingString:key]])&&&&&&&&&{&&&&&&&&&&&// Get type&&&&&&&&&&&Class propertyClass = nil;&&&&&&&&&&&char *typeEncoding = property_copyAttributeValue(property, "T");&&&&&&&&&&&switch (typeEncoding[0])&&&&&&&&&&&{&&&&&&&&&&&&&case 'c': // Numeric types&&&&&&&&&&&&&case 'i':&&&&&&&&&&&&&case 's':&&&&&&&&&&&&&case 'l':&&&&&&&&&&&&&case 'q':&&&&&&&&&&&&&case 'C':&&&&&&&&&&&&&case 'I':&&&&&&&&&&&&&case 'S':&&&&&&&&&&&&&case 'L':&&&&&&&&&&&&&case 'Q':&&&&&&&&&&&&&case 'f':&&&&&&&&&&&&&case 'd':&&&&&&&&&&&&&case 'B':&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSNumber class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '*': // C-String&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSString class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '@': // Object&&&&&&&&&&&&&{&&&&&&&&&&&&&&&//TODO: get class name&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '{': // Struct&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = [NSValue class];&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&&&case '[': // C-Array&&&&&&&&&&&&&case '(': // Enum&&&&&&&&&&&&&case '#': // Class&&&&&&&&&&&&&case ':': // Selector&&&&&&&&&&&&&case '^': // Pointer&&&&&&&&&&&&&case 'b': // Bitfield&&&&&&&&&&&&&case '?': // Unknown type&&&&&&&&&&&&&default:&&&&&&&&&&&&&{&&&&&&&&&&&&&&&propertyClass = nil; // Not supported by KVC&&&&&&&&&&&&&&&break;&&&&&&&&&&&&&}&&&&&&&&&&&}&&&&&&&&&&&free(typeEncoding);&&&&&&&&&&&// If known type, add to dictionary&&&&&&&&&&&if (propertyClass) dictionary[propertyName] = propertyClass;&&&&&&&&&}&&&&&&&&&free(ivar);&&&&&&&}&&&&&}&&&&&free(properties);&&&&&subclass = [subclass superclass];&&&}&&&// Cache and return dictionary&&&objc_setAssociatedObject([self class], _cmd, dictionary, &&&&&OBJC_ASSOCIATION_RETAIN_NONATOMIC);&&&return dictionary;&}
最难的部分已经完成了。现在,要实现NSSecureCoding,只要将initWithCoder:方法中之前写的自动编码实现的部分,改为在解析时考虑到属性的类就可以了。此外,还需让supportsSecureCoding方法返回YES:
+ (BOOL)supportsSecureCoding
return YES;
- (id)initWithCoder:(NSCoder *)coder
if ((self = [super init]))
// Decode the property values by key, specifying the expected class
[[self propertyClassesByName] enumerateKeysAndObjectsUsingBlock:(void (^)(NSString *key, Class propertyClass, BOOL *stop)) {
id object = [aDecoder decodeObjectOfClass:propertyClass forKey:key];
if (object) [self setValue:object forKey:key];
- (void)encodeWithCoder:(NSCoder *)aCoder
for (NSString *key in [self propertyClassesByName])
id object = [self valueForKey:key];
if (object) [aCoder encodeObject:object forKey:key];
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
+ (BOOL)supportsSecureCoding&{&&&return YES;&}&- (id)initWithCoder:(NSCoder *)coder&{&&&if ((self = [super init]))&&&{&&&&&// Decode the property values by key, specifying the expected class&&&&&[[self propertyClassesByName] enumerateKeysAndObjectsUsingBlock:(void (^)(NSString *key, Class propertyClass, BOOL *stop)) {&&&&&&&id object = [aDecoder decodeObjectOfClass:propertyClass forKey:key];&&&&&&&if (object) [self setValue:object forKey:key];&&&&&}];&&&}&&&return self;&}&- (void)encodeWithCoder:(NSCoder *)aCoder&{&&&for (NSString *key in [self propertyClassesByName])&&&{&&&&&id object = [self valueForKey:key];&&&&&if (object) [aCoder encodeObject:object forKey:key];&&&}&}
这样就得到了一个用于描述模型对象的简单的基类,并且它以正确的方式支持NSSecureCoding。此外,你可以使用我的扩展,它利用这种方法自动给没有实现NSCoding 和 NSSecureCoding协议的对象添加对它们的支持。
关于作者:
可能感兴趣的话题
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线内容团队正试图以我们微薄的力量,把优秀的原创文章和译文分享给读者,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线

我要回帖

更多关于 in case 的文章

 

随机推荐