KVC/KVO 进阶(四)KVC是如何处理异常的

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” 引起的崩溃
未完 待续

Guess you like

Origin blog.csdn.net/u014641631/article/details/52957578