在实际开发及应用过程中,经常会遇到通过外部数据构造的字典的键与自定义数据模型类中属性的名称或是个数不一致的情况。
例如:从外部获得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 + 165 1 libobjc.A.dylib 0x000000010ddefbb7 objc_exception_throw + 45 2 CoreFoundation 0x000000010e1588a9 -[NSException raise] + 9 3 Foundation 0x000000010d984b53 -[NSObject(NSKeyValueCoding) setValue:forKey:] + 259 4 Foundation 0x000000010d9c6fad -[NSObject(NSKeyValueCoding) setValuesForKeysWithDictionary:] + 261 5 2015-05-05-setValueforUndefinedKey 0x000000010d8b94bd -[Weather initWithDictionary:] + 141 6 2015-05-05-setValueforUndefinedKey 0x000000010d8b9557 +[Weather weatherWithDictionary:] + 87 7 2015-05-05-setValueforUndefinedKey 0x000000010d8b9925 -[ViewController viewDidLoad] + 277 8 UIKit 0x000000010e683210 -[UIViewController loadViewIfRequired] + 738 9 UIKit 0x000000010e68340e -[UIViewController view] + 27 10 UIKit 0x000000010e59e2c9 -[UIWindow addRootViewControllerViewIfPossible] + 58 11 UIKit 0x000000010e59e68f -[UIWindow _setHidden:forced:] + 247 12 UIKit 0x000000011bb4a175 -[UIWindowAccessibility _orderFrontWithoutMakingKey] + 68 13 UIKit 0x000000010e5aae21 -[UIWindow makeKeyAndVisible] + 42 14 UIKit 0x000000010e54e457 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 2732 15 UIKit 0x000000010e5511de -[UIApplication _runWithMainScene:transitionContext:completion:] + 1349 16 UIKit 0x000000010e5500d5 -[UIApplication workspaceDidEndTransaction:] + 179 17 FrontBoardServices 0x0000000110d575e5 __31-[FBSSerialQueue performAsync:]_block_invoke_2 + 21 18 CoreFoundation 0x000000010e08c41c __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12 19 CoreFoundation 0x000000010e082165 __CFRunLoopDoBlocks + 341 20 CoreFoundation 0x000000010e081f25 __CFRunLoopRun + 2389 21 CoreFoundation 0x000000010e081366 CFRunLoopRunSpecific + 470 22 UIKit 0x000000010e54fb42 -[UIApplication _run] + 413 23 UIKit 0x000000010e552900 UIApplicationMain + 1282 24 2015-05-05-setValueforUndefinedKey 0x000000010d8b9c7f main + 111 25 libdyld.dylib 0x0000000110727145 start + 1 26 ??? 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 = 0 key = state2, value = 1 [cityname, tem1, tem2] = [beijing, 25, 14]
总结:
当需要将字典转为模型的时候,如果遇到字典的键与模型类中属性的名称或是个数不一致的情况,可以尝试重写setValue:forUndefinedKey:方法。