KVC是如何处理异常的
前言
上篇文章大致阐述了KVC在key正确情况下的执行过程,这篇来说说当你任性的用一个错误的key来玩KVC,KVC是如何反击你的。
正文
KVC中最常见的异常情况:
1、不小心使用了错误的Key(你拿一个根本不存在的key在 取/赋 值)
2、在非指针对象赋值中不小心传递了nil的值 ( 这里 说的 nil 和 咱们经常讨论的 setvalue和setobject的区别” 不是一个话题)
KVC中有没有一套监管机制来专门来处理这些异常呢? 幸运的是有。
我们先来看 “在对非指针对象赋值中不小心传递了nil的值” 引起的崩溃
上代码: Persion.h
@class Car;
@interface Persion : NSObject
{
NSString *_adress;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Car *car;
@property (nonatomic, assign) int age;
@end
Persion.m
@implementation Persion
@synthesize name = _name;
@synthesize age = _age;
// 也许有人会问:都xcode8了,你还在写synthesize,这里为什么要写synthesize 和kvc有什么关系 可以查看我之前的博客,本章博客不在论述。
- (void)setName:(NSString *)name {
NSLog(@"%s",__func__);
_name = name;
}
- (NSString *)name {
NSLog(@"%s",__func__);
return _name;
}
- (void)setAge:(int)age {
NSLog(@"%s",__func__);
_age = age;
}
- (int)age {
NSLog(@"%s",__func__);
return _age;
}
+ (BOOL)accessInstanceVariablesDirectly {
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
- (id)valueForKey:(NSString *)key {
NSLog(@"%s",__func__);
return [super valueForKey:key];
}
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%s",__func__);
[super setValue:value forKey:key];
NSLog(@"%s",__func__);
}
@end
ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Persion *persion = [[Persion alloc] init];
[persion setValue:nil forKey:@"name"];
[persion valueForKey:@"_name"];
NSLog(@"%@",persion.name);
}
@end
程序运行结果
2016-11-11 16:28:34.351 KVC[13543:307521] -[Persion setValue:forKey:]
2016-11-11 16:28:34.351 KVC[13543:307521] -[Persion setName:]
2016-11-11 16:28:34.352 KVC[13543:307521] -[Persion setValue:forKey:]
2016-11-11 16:28:34.352 KVC[13543:307521] -[Persion valueForKey:]
2016-11-11 16:28:34.352 KVC[13543:307521] +[Persion accessInstanceVariablesDirectly]
2016-11-11 16:28:34.352 KVC[13543:307521] -[Persion name]
2016-11-11 16:28:34.352 KVC[13543:307521] (null)
卧槽,这里我传入的nil,你上面不是说crash吗, 为什么程序不崩溃?
请耐心往下看,仔细和下面的崩溃代码,进行对比。
ViewController.m
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Persion *persion = [[Persion alloc] init];
[persion setValue:nil forKey:@"name"];
[persion valueForKey:@"_name"];
[persion setValue:nil forKey:@"age"];
NSLog(@"%@",persion.name);
}
@end
我们追加一行 [persion setValue:nil forKey:@”age”];
运行结果
2016-11-11 16:30:50.886 KVC[13592:309317] -[Persion setValue:forKey:]
2016-11-11 16:30:50.888 KVC[13592:309317] -[Persion setName:]
2016-11-11 16:30:50.888 KVC[13592:309317] -[Persion setValue:forKey:]
2016-11-11 16:30:50.889 KVC[13592:309317] -[Persion valueForKey:]
2016-11-11 16:30:50.889 KVC[13592:309317] +[Persion accessInstanceVariablesDirectly]
2016-11-11 16:30:50.889 KVC[13592:309317] -[Persion setValue:forKey:]
2016-11-11 16:30:50.894 KVC[13592:309317] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<Persion 0x608000051700> setNilValueForKey]: could not set nil as the value for the key age.'
*** First throw call stack:
卧槽,这次崩溃了。这是为什么呢?
细心比较,我们发现, age 是 int 类型 name 是NSString 类型 ,也就是说:如果 key是非指针类型的对象,setValue 的时候为nil,程序一定会崩溃,反之, 如果key是 指针类型的对象,setValue 允许为nil,程序正常。
再接着看下面代码:
Persion.m 修改后的代码
@implementation Persion
@synthesize name = _name;
@synthesize age = _age;
- (void)setName:(NSString *)name {
NSLog(@"%s",__func__);
_name = name;
}
- (NSString *)name {
NSLog(@"%s",__func__);
return _name;
}
- (void)setAge:(int)age {
NSLog(@"%s",__func__);
_age = age;
}
- (int)age {
NSLog(@"%s",__func__);
return _age;
}
+ (BOOL)accessInstanceVariablesDirectly {
NSLog(@"%s",__func__);
return [super accessInstanceVariablesDirectly];
}
- (id)valueForKey:(NSString *)key {
NSLog(@"%s",__func__);
return [super valueForKey:key];
}
- (void)setValue:(id)value forKey:(NSString *)key {
NSLog(@"%s",__func__);
[super setValue:value forKey:key];
NSLog(@"%s",__func__);
}
// 加入这个方法,防止崩溃,在这个函数里面,我们可以做一些特殊处理
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"%s",__func__);
}
@end
ViewController.m 代码不变
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Persion *persion = [[Persion alloc] init];
[persion setValue:nil forKey:@"name"];
[persion valueForKey:@"_name"];
[persion setValue:nil forKey:@"age"];
NSLog(@"%@",persion.name);
}
@end
运行结果
2016-11-11 16:34:56.107 KVC[13683:312567] -[Persion setValue:forKey:]
2016-11-11 16:34:56.107 KVC[13683:312567] -[Persion setName:]
2016-11-11 16:34:56.108 KVC[13683:312567] -[Persion setValue:forKey:]
2016-11-11 16:34:56.108 KVC[13683:312567] -[Persion valueForKey:]
2016-11-11 16:34:56.108 KVC[13683:312567] +[Persion accessInstanceVariablesDirectly]
2016-11-11 16:34:56.109 KVC[13683:312567] -[Persion setValue:forKey:]
2016-11-11 16:34:56.109 KVC[13683:312567] -[Persion setNilValueForKey:]
2016-11-11 16:34:56.109 KVC[13683:312567] -[Persion setValue:forKey:]
2016-11-11 16:34:56.109 KVC[13683:312567] -[Persion name]
2016-11-11 16:34:56.110 KVC[13683:312567] (null)
卧槽, 程序 居然不崩溃了。 只是因为 刚刚我们在 Persion类中,追加了这个函数
// 加入这个方法,防止崩溃,在这个函数里面,我们可以做一些特殊处理
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"%s",__func__);
}
程序在尝试匹配 非指针类型的key无果后,抛出异常,但是我们重写setNilValueForKey:就没问题了。
通过上面的输出台,我们发现 [Persion setNilValueForKey:] 被调用 。
官方原理
KVC不允许你要在调用setValue:forKey:(或者keyPath)时 对一个对象(非指针类型)的传递一个nil的值。很简单,因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
我们再来看 “不小心使用了错误的Key” 引起的崩溃
未完 待续