在iOS平台上,要操縱JSON數據並不困難,但是,我們還有更簡單的解決方案,使用KVC,全稱是Key-Value Coding。
假設開發者(你)開發了一款應用,它的數據來自於外部對Web服務,要從Web服務中取回一些JSON數據,數據如下:
{count: 3, sum: 9.0, average: 3.0}
要從服務器中獲取數據,需要調用NSJSONSerializationalization的JSONObjectWithData方法,並從解序列化的字典中取回數據,比如:
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSLog(@%d, [[dictionary objectForKey:@count] intValue]); // prints 3Rd
NSLog(@%.1f, [[dictionary objectForKey:@sum] doubleValue]); // prints 9.0
NSLog(@%.1f, [[dictionary objectForKey:@average] doubleValue]); // prints 3.0
但是,上面的值比較分散,在做應用開發時,或許想與強類型的數據對象直接交互,這樣會更加簡單。比如,你或許想要創建一個Statistics統計類,來代表通過Web服務返回的數據類型,如下:
@interface Statistics : NSObject
@property (nonatomic) int count;
@property (nonatomic) double sum;
@property (nonatomic) double average;
@end
然後可以從字典中提取值來填充以上的對象:
Statistics *statistics = [[Statistics alloc] init];
statistics.count = [[dictionary objectForKey:@count] intValue];
statistics.sum = [[dictionary objectForKey:@sum] doubleValue];
statistics.average = [[dictionary objectForKey:@average] doubleValue];
為了讓事情更簡單,避免代碼重復,可以把這段代碼放在Statistics類的初始化中:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
self.count = [[dictionary objectForKey:@count] intValue];
self.sum = [[dictionary objectForKey:@sum] doubleValue];
self.average = [[dictionary objectForKey:@average] doubleValue];
}
return self;
}
代碼綁定JSON響應到Statistics實例,如下:
Statistics *statistics = [[Statistics alloc] initWithDictionary:dictionary];
在任何情況下,你都可以使用此強類型的數據對象的屬性來訪問從服務器返回的數據:
NSLog(@%d, statistics.count); // prints 3
NSLog(@%.1f, statistics.sum); // prints 9.0
NSLog(@%.1f, statistics.average); // prints 3.0
上面的代碼工作正常,而且把JSON數據映射到強類型的數據對象是非常適合的方法。
但是,還有更簡單的解決方案:KVC。NSObject的setValuesForKeysWithDictionary:方法可用於將給定字典上所有的值自動映射到對象的屬性。使用這種方法,initWithDictionary:方法簡化如下:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
無需手動映射字典到項到屬性值中,使用適合的名字和類型來聲明屬性就足夠了,下面的代碼中Swift中工作良好:
class Statistics: NSObject {
var count: Int = 0
var sum: Double = 0
var average: Double = 0
init(dictionary: [String:AnyObject]) {
super.init()
setValuesForKeysWithDictionary(dictionary);
}
}
此外,如果你需要自定義屬性名或屬性值的分配,那麼可以簡單的重寫setValue:forKey:方法。比如,假設服務器以不同的名字來引用平均屬性:
{count: 3, sum: 9.0, mean: 3.0}
可以重寫setValue:forKey:方法,確保值能正確的映射到屬性中:
- (void)setValue:(id)value forKey:(NSString *)key {
if ([key isEqual:@mean]) {
key = @average;
}
[super setValue:value forKey:key];
}
最後,你可以使用KVC來忽略你不想要的值。比如,假設服務器的響應還包含了名為“median”的屬性:
{count: 3, sum: 9.0, average: 3.0, median: 3.0}
由於Statistics類沒有定義“Median”屬性,那麼setValuesForKeysWithDictionary:方法會拋出NSUnknownKeyException異常。要避免拋出此異常,可以簡單的重寫setValue:forUndefinedKey::方法。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
// No-op
}