KVC與setValue:forUndefinedKey:方法,kvcsetvalue
在實際開發及應用過程中,經常會遇到通過外部資料構造的字典的鍵要多於自訂資料模型中屬性的個數的情況。
例如:從外部獲得JSON格式的資料包含5個鍵,如下所示:
{ "cityname" : "beijing", "state1" : "0", "state2" : "1", "tem1" : "25", "tem2" : "14",}
而與之對應的模型只包含3個屬性:
/** 城市名 */@property (copy, nonatomic) NSString *cityname;/** 最低溫度 */@property (copy, nonatomic) NSNumber *tem1;/** 最高溫度 */@property (copy, nonatomic) NSNumber *tem2;
整個樣本程式的代碼如下:
控制器ViewController.m:
#import "ViewController.h"#import "Weather.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // 讀取JSON格式的外部資料 NSURL *url = [[NSBundle mainBundle] URLForResource:@"weather" withExtension:@"json"]; NSData *data = [NSData dataWithContentsOfURL:url]; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:NULL]; // 資料模型執行個體 Weather *w = [Weather weatherWithDictionary:dict]; NSLog(@"%@", w);}@end
模型類Weather.h:
#import <Foundation/Foundation.h>@interface Weather : NSObject/** 城市名 */@property (copy, nonatomic) NSString *cityname;/** 最低溫度 */@property (copy, nonatomic) NSNumber *tem1;/** 最高溫度 */@property (copy, nonatomic) NSNumber *tem2;- (instancetype)initWithDictionary:(NSDictionary *)dict;+ (instancetype)weatherWithDictionary:(NSDictionary *)dict;@end
模型類Weather.m:
#import "Weather.h"@implementation Weather- (instancetype)initWithDictionary:(NSDictionary *)dict { self = [super init]; if (nil != self) { [self setValuesForKeysWithDictionary:dict]; } return self;}+ (instancetype)weatherWithDictionary:(NSDictionary *)dict { return [[self alloc] initWithDictionary:dict];}- (NSString *)description { return [NSString stringWithFormat:@"[cityname, tem1, tem2] = [%@, %@, %@]", self.cityname, self.tem1, self.tem2];}@end
此時,如果運行程式,會報告以下錯誤資訊:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key state1.'*** First throw call stack:(0 CoreFoundation 0x000000010e158c65 __exceptionPreprocess + 1651 libobjc.A.dylib 0x000000010ddefbb7 objc_exception_throw + 452 CoreFoundation 0x000000010e1588a9 -[NSException raise] + 93 Foundation 0x000000010d984b53 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 2594 Foundation 0x000000010d9c6fad -[NSObject(NSKeyValueCoding) setValuesForKeysWithDictionary:] + 2615 2015-05-05-setValueforUndefinedKey 0x000000010d8b94bd -[Weather initWithDictionary:] + 1416 2015-05-05-setValueforUndefinedKey 0x000000010d8b9557 +[Weather weatherWithDictionary:] + 877 2015-05-05-setValueforUndefinedKey 0x000000010d8b9925 -[ViewController viewDidLoad] + 2778 UIKit 0x000000010e683210 -[UIViewController loadViewIfRequired] + 7389 UIKit 0x000000010e68340e -[UIViewController view] + 2710 UIKit 0x000000010e59e2c9 -[UIWindow addRootViewControllerViewIfPossible] + 5811 UIKit 0x000000010e59e68f -[UIWindow _setHidden:forced:] + 24712 UIKit 0x000000011bb4a175 -[UIWindowAccessibility _orderFrontWithoutMakingKey] + 6813 UIKit 0x000000010e5aae21 -[UIWindow makeKeyAndVisible] + 4214 UIKit 0x000000010e54e457 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 273215 UIKit 0x000000010e5511de -[UIApplication _runWithMainScene:transitionContext:completion:] + 134916 UIKit 0x000000010e5500d5 -[UIApplication workspaceDidEndTransaction:] + 17917 FrontBoardServices 0x0000000110d575e5 __31-[FBSSerialQueue performAsync:]_block_invoke_2 + 2118 CoreFoundation 0x000000010e08c41c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 1219 CoreFoundation 0x000000010e082165 __CFRunLoopDoBlocks + 34120 CoreFoundation 0x000000010e081f25 __CFRunLoopRun + 238921 CoreFoundation 0x000000010e081366 CFRunLoopRunSpecific + 47022 UIKit 0x000000010e54fb42 -[UIApplication _run] + 41323 UIKit 0x000000010e552900 UIApplicationMain + 128224 2015-05-05-setValueforUndefinedKey 0x000000010d8b9c7f main + 11125 libdyld.dylib 0x0000000110727145 start + 126 ??? 0x0000000000000001 0x0 + 1)libc++abi.dylib: terminating with uncaught exception of type NSException
當使用setValuesForKeysWithDictionary:方法時,對於資料模型中缺少的、不能與任何鍵配對的屬性的時候,系統會自動調用setValue:forUndefinedKey:這個方法,該方法預設的實現會引發一個NSUndefinedKeyExceptiony異常。
如果想要程式在運行過程中不引發任何異常資訊且正常工作,可以讓資料模型類重寫setValue:forUndefinedKey:方法以覆蓋預設實現,而且可以通過這個方法的兩個參數獲得無法配對索引值。
修改後的模型Weather.m:
#import "Weather.h"@implementation Weather- (instancetype)initWithDictionary:(NSDictionary *)dict { self = [super init]; if (nil != self) { [self setValuesForKeysWithDictionary:dict]; } return self;}// 重寫setValue:forUndefinedKey:方法- (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"key = %@, value = %@", key, value);}+ (instancetype)weatherWithDictionary:(NSDictionary *)dict { return [[self alloc] initWithDictionary:dict];}- (NSString *)description { return [NSString stringWithFormat:@"[cityname, tem1, tem2] = [%@, %@, %@]", self.cityname, self.tem1, self.tem2];}@end
本例中,重寫setValue:forUndefinedKey:方法但不對任何未能配對的索引值做任何實質性操作以忽略它們。當然,也可以在方法體中對索引值進行處理。
修改完畢後,運行程式輸出如下:
key = state1, value = 0key = state2, value = 1[cityname, tem1, tem2] = [beijing, 25, 14]