·您现在的位置: 云翼网络 >> 文章中心 >> 网站建设 >> app软件开发 >> IOS开发 >> KVC与setValue:forUndefinedKey:方法

KVC与setValue:forUndefinedKey:方法

作者:佚名      IOS开发编辑:admin      更新时间:2022-07-23

在实际开发及应用过程中,经常会遇到通过外部数据构造的字典的键与自定义数据模型类中属性的名称或是个数不一致的情况。

例如:从外部获得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:方法。