http://blog.jobbole.com/67655/
NSCoding是把數(shù)據(jù)存儲(chǔ)在iOS和Mac
OS上的一種極其簡(jiǎn)單和方便的方式耗美,它把模型對(duì)象直接轉(zhuǎn)變成一個(gè)文件商架,然后再把這個(gè)文件重新加載到內(nèi)存里,并不需要任何文件解析和序列化的邏輯弃鸦。如果要把對(duì)象保存到一個(gè)數(shù)據(jù)文件中(假設(shè)這個(gè)對(duì)象實(shí)現(xiàn)了NSCoding協(xié)議),那么你可以像下面這樣做:
C++
Foo *someFoo = [[Foo alloc] init];
[NSKeyedArchiver archiveRootObject:someFoo toFile:someFile];
1
2Foo*someFoo=[[Fooalloc]init];
[NSKeyedArchiverarchiveRootObject:someFootoFile:someFile];
稍后再加載它:
C++
Foo *someFoo = [NSKeyedUnarchiver unarchiveObjectWithFile:someFile];
1
Foo*someFoo=[NSKeyedUnarchiverunarchiveObjectWithFile:someFile];
這樣做對(duì)于編譯進(jìn)APP里的資源來說是可以的(例如nib文件颜说,它在底層使用了NSCoding)髓梅,但是使用NSCoding來讀寫用戶數(shù)據(jù)文件的問題在于酝锅,把全部的類編碼到一個(gè)文件里,也就間接地給了這個(gè)文件訪問你APP里面實(shí)例類的權(quán)限场绿。
雖然你不能在一個(gè)NSCoded文件里(至少在iOS中的)存儲(chǔ)可執(zhí)行代碼澎粟,但是一名黑客可以使用特制地文件騙過你的APP進(jìn)入到實(shí)例化類中啸盏,這是你從沒打算做的,或者是你想要在另一個(gè)不同的上下文時(shí)才做的骑祟。盡管以這種方式造成實(shí)際性的破壞很難回懦,但是無疑會(huì)導(dǎo)致用戶的APP崩潰掉或者數(shù)據(jù)丟失气笙。
在iOS6中,蘋果引入了一個(gè)新的協(xié)議怯晕,是基于NSCoding的潜圃,叫做NSSecureCoding。NSSecureCoding和NSCoding是一樣的舟茶,除了在解碼時(shí)要同時(shí)指定key和要解碼的對(duì)象的類秉犹,如果要求的類和從文件中解碼出的對(duì)象的類不匹配,NSCoder會(huì)拋出異常稚晚,告訴你數(shù)據(jù)已經(jīng)被篡改了。
大部分支持NSCoding的系統(tǒng)對(duì)象都已經(jīng)升級(jí)到支持NSSecureCoding了型诚,所以能安全地寫有關(guān)歸檔的代碼客燕,你可以確保正在加載的數(shù)據(jù)文件是安全的。實(shí)現(xiàn)的方式如下:
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];
1
2
3
4
5
6
7// Set up NSKeyedUnarchiver to use secure coding
NSData*data=[NSDatadataWithContentsOfFile:someFile];
NSKeyedUnarchiver*unarchiver=[[NSKeyedUnarchiveralloc]initForReadingWithData:data];
[unarchiversetRequiresSecureCoding:YES];
// Decode object
Foo*someFoo=[unarchiverdecodeObjectForKey:NSKeyedArchiveRootObjectKey];
注意一下狰贯,如果要讓編寫歸檔的代碼是安全的也搓,那么存儲(chǔ)在文件中的每一個(gè)對(duì)象都要實(shí)現(xiàn)NSSecureCoding協(xié)議,否則會(huì)有異常拋出涵紊。如果要告訴框架自定義的類支持NSSecureCoding協(xié)議傍妒,那么你必須在initWithCoder:
method方法中實(shí)現(xiàn)新的解碼邏輯,并且supportsSecureCodin方法要返回YES摸柄。encodeWithCoder:方法沒有變化颤练,因?yàn)榕c安全相關(guān)的事是圍繞加載進(jìn)行的,而不是保存:
C++
@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
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@interfaceFoo:NSObject
@property(nonatomic,strong)NSNumber*property1;
@property(nonatomic,copy)NSArray*property2;
@property(nonatomic,copy)NSString*property3;
@end
@implementationFoo
+(BOOL)supportsSecureCoding
{
returnYES;
}
-(id)initWithCoder:(NSCoder*)coder
{
if((self=[superinit]))
{
// Decode the property values by key, specifying the expected class
_property1=[coderdecodeObjectOfClass:[NSNumberclass]forKey:@"property1"];
_property2=[coderdecodeObjectOfClass:[NSArrayclass]forKey:@"property2"];
_property3=[coderdecodeObjectOfClass:[NSStringclass]forKey:@"property3"];
}
returnself;
}
-(void)encodeWithCoder:(NSCoder*)coder
{
// Encode our ivars using string keys as normal
[coderencodeObject:_property1forKey:@"property1"];
[coderencodeObject:_property2forKey:@"property2"];
[coderencodeObject:_property3forKey:@"property3"];
}
@end
幾周前驱负,我寫了一篇關(guān)于如何自動(dòng)實(shí)現(xiàn)NSCoding的文章嗦玖,它利用反射機(jī)制確定運(yùn)行時(shí)類的屬性。
這是一種給所有的模型對(duì)象添加NSCoding支持的很好的方式跃脊,在initWithCoder:/encodeWithCoder:
方法中宇挫,你不再需要寫重復(fù)的并且容易出錯(cuò)的代碼了。但是我們使用的方法沒有支持NSSecureCoding酪术,因?yàn)槲覀儾淮蛩阍趯?duì)象被加載時(shí)校驗(yàn)其類型器瘪。
那么怎么改善這個(gè)自動(dòng)NSCoding系統(tǒng),使其以正確的方式支持NSSecureCoding呢绘雁?
回想一下橡疼,最開始的實(shí)現(xiàn)原理是利用class_copyPropertyList() 和 property_getName()這樣兩個(gè)運(yùn)行時(shí)方法,產(chǎn)生屬性名稱列表咧七,我們?cè)侔阉鼈冊(cè)跀?shù)組中排序:
C++
// Import the Objective-C runtime headers
#import
- (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;
}
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// Import the Objective-C runtime headers
#import
-(NSArray*)propertyNames
{
// Get the list of properties
unsignedintpropertyCount;
objc_property_t*properties=class_copyPropertyList([selfclass],
&propertyCount);
NSMutableArray*array=[NSMutableArrayarrayWithCapacity:propertyCount];
for(inti=0;i
{
// Get property name
objc_property_tproperty=properties[i];
constchar*propertyName=property_getName(property);
NSString*key=@(propertyName);
// Add to array
[arrayaddObject:key];
}
// Remember to free the list because ARC doesn't do that for us
free(properties);
returnarray;
}
使用KVC(鍵-值編碼)衰齐,我們能夠利用名稱設(shè)置和獲取一個(gè)對(duì)象的所有屬性,并且在一個(gè)NSCoder對(duì)象中對(duì)這些屬性進(jìn)行編碼/解碼继阻。
為了要實(shí)現(xiàn)NSSecureCoding耻涛,我們要遵循同樣的原則废酷,但是不僅僅是獲取屬性名,還需要獲取它們的類型抹缕。幸運(yùn)地是澈蟆,Objective C運(yùn)行時(shí)存儲(chǔ)了類的屬性類型的詳細(xì)信息,所以可以很容易和名字一起取到這些數(shù)據(jù)卓研。
一個(gè)類的屬性可以是基本數(shù)據(jù)類型(例如整型趴俘、布爾類型和結(jié)構(gòu)體),或者對(duì)象(例如字符串奏赘、數(shù)組等等)寥闪。KVC中的valueForKey: and
setValue:forKey:方法實(shí)現(xiàn)了對(duì)基本類型的自動(dòng)“裝箱”,也就是說它們會(huì)把整型磨淌、布爾型和結(jié)構(gòu)體各自轉(zhuǎn)變成NSNumber和NSValue對(duì)象疲憋。這使事情變得簡(jiǎn)單了很多,因?yàn)槲覀冎灰幚硌b箱過的類型(對(duì)象)即可梁只,所以我們可以聲明屬性類型為類缚柳,而不用為不同的屬性類型調(diào)用不同的解碼方法。
盡管運(yùn)行時(shí)方法沒有提供已裝箱的類名搪锣,但是它們提供了類型編碼—一種特殊格式化的C風(fēng)格的字符串秋忙,它包含了類型信息(與@encode(var);返回的形式一樣)。因?yàn)闆]有方法自動(dòng)獲取到基本類型對(duì)應(yīng)的裝箱過的類构舟,所以我們需要解析這個(gè)字符串灰追,然后指定其合適的類型。
類型編碼字符串形式的文檔說明在這里狗超。
第一個(gè)字母代表了基本類型监嗜。Objective
C使用一個(gè)唯一的字母表示每一個(gè)支持的基本類型,例如’i’表示integer抡谐,’f’表示float裁奇,’d’表示double,等等麦撵。對(duì)象用’@’表示(緊跟著的是類名)刽肠,還有其他一些不常見的類型,例如’:’表示selectors免胃,’#’表示類音五。
結(jié)構(gòu)體和聯(lián)合體表示為大括號(hào)里面的表達(dá)式。只有幾種類型是KVC機(jī)制所支持的羔沙,但是支持的那些類通常被裝箱為NSValue對(duì)象躺涝,所以可用一種方式處理以’{’開頭的任何值。
如果根據(jù)字符串的首字母來轉(zhuǎn)換扼雏,那么我們可以處理所有已知的類型:
C++
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);
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99ClasspropertyClass=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=[NSNumberclass];
break;
}
case'*':// C-String
{
propertyClass=[NSStringclass];
break;
}
case'@':// Object
{
//TODO: get class name
break;
}
case'{':// Struct
{
propertyClass=[NSValueclass];
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);
如果要處理’@’類型坚嗜,則需要提去出類名夯膀。類名可能包括協(xié)議(實(shí)際上我們并不需要用到),所以劃分字符串拿準(zhǔn)確的類名苍蔬,然后使用NSClassFromString得到類:
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);
}
break;
}
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
40case'@':
{
// 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
NSRangerange=[namerangeOfString:@"<"];
if(range.location!=NSNotFound)
{
name=[namesubstringToIndex:range.location];
}
// Get class from name, or default to NSObject if no name is found
propertyClass=NSClassFromString(name)?:[NSObjectclass];
free(cName);
}
break;
}
最后诱建,把上面的解析過程和前面實(shí)現(xiàn)的propertyNames方法結(jié)合起來,創(chuàng)建一個(gè)方法返回屬性類的字典碟绑,屬性名稱作為字典的鍵俺猿。下面是完成的實(shí)現(xiàn)過程:
- (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;
}
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193-(NSDictionary *)propertyClassesByName
{
// Check for a cached value (we use _cmd as the cache key,
// which represents @selector(propertyNames))
NSMutableDictionary *dictionary=objc_getAssociatedObject([selfclass],_cmd);
if(dictionary)
{
returndictionary;
}
// Loop through our superclasses until we hit NSObject
dictionary=[NSMutableDictionarydictionary];
Classsubclass=[selfclass];
while(subclass!=[NSObjectclass])
{
unsignedintpropertyCount;
objc_property_t *properties=class_copyPropertyList(subclass,
&propertyCount);
for(inti=0;i<propertyCount;i++)
{
// Get property name
objc_property_tproperty=properties[i];
constchar*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([ivarNameisEqualToString:key]||
[ivarNameisEqualToString:[@"_"stringByAppendingString:key]])
{
// Get type
ClasspropertyClass=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=[NSNumberclass];
break;
}
case'*': // C-String
{
propertyClass=[NSStringclass];
break;
}
case'@': // Object
{
//TODO: get class name
break;
}
case'{': // Struct
{
propertyClass=[NSValueclass];
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=[subclasssuperclass];
}
// Cache and return dictionary
objc_setAssociatedObject([selfclass],_cmd,dictionary,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
returndictionary;
}
最難的部分已經(jīng)完成了。現(xiàn)在格仲,要實(shí)現(xiàn)NSSecureCoding押袍,只要將initWithCoder:方法中之前寫的自動(dòng)編碼實(shí)現(xiàn)的部分,改為在解析時(shí)考慮到屬性的類就可以了凯肋。此外伯病,還需讓supportsSecureCoding方法返回YES:
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];
}];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
for (NSString *key in [self propertyClassesByName])
{
id object = [self valueForKey:key];
if (object) [aCoder encodeObject:object forKey:key];
}
}
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+(BOOL)supportsSecureCoding
{
returnYES;
}
-(id)initWithCoder:(NSCoder*)coder
{
if((self=[superinit]))
{
// Decode the property values by key, specifying the expected class
[[selfpropertyClassesByName]enumerateKeysAndObjectsUsingBlock:(void(^)(NSString*key,ClasspropertyClass,BOOL*stop)){
idobject=[aDecoderdecodeObjectOfClass:propertyClassforKey:key];
if(object)[selfsetValue:objectforKey:key];
}];
}
returnself;
}
-(void)encodeWithCoder:(NSCoder*)aCoder
{
for(NSString*keyin[selfpropertyClassesByName])
{
idobject=[selfvalueForKey:key];
if(object)[aCoderencodeObject:objectforKey:key];
}
}
這樣就得到了一個(gè)用于描述模型對(duì)象的簡(jiǎn)單的基類,并且它以正確的方式支持NSSecureCoding否过。此外,你可以使用我的AutoCoding擴(kuò)展惭蟋,它利用這種方法自動(dòng)給沒有實(shí)現(xiàn)NSCoding 和 NSSecureCoding協(xié)議的對(duì)象添加對(duì)它們的支持苗桂。