版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.09.09 |
前言
KVC
相信大家再熟悉不过了,键值编码,可以解决很多问题,包括视图上的给UITextField
占位文字颜色大小进行设置等等,还有很多地方可以用KVC,接下来几篇我们就深度解析一下KVC。总结一下,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。还是老规矩,由面到点,由浅到深,希望对大家有所帮助。感兴趣的可以看我写的另外几篇文章。
1. KVC解析(一) —— 基本了解
2. KVC解析(二) —— 不可不知的赋值深层次原理
3. KVC解析(三) —— 不可不知的取值深层次原理
4. KVC解析(四) —— keyPath的深度解析
为什么要做异常处理?
在使用KVC的时候,由于人为操作的错误,很多时候会碰到异常,这就需要我们人工进行干预,否则就会崩溃,常见的比如说找key的时候出现错误等,这些需要我们进行处理。
几种典型的异常处理
1. 找不到key
这种情况就会直接奔溃,抛出异常,比如说:
#import "JJKVCExceptionVC.h"
@interface JJKVCExceptionVC ()
@property (nonatomic, copy) NSString *name;
@end
@implementation JJKVCExceptionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self demoExceptionOne];
}
#pragma mark - Object Private Function
//异常场景1:找不到对应的key
- (void)demoExceptionOne
{
[self setValue:nil forKey:@"person"];
}
@end
下面看输出结果
2017-09-09 11:40:08.107 JJOC[3372:86627] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<JJKVCExceptionVC 0x7fd09cf04bc0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key person.'
*** First throw call stack:
(
0 CoreFoundation 0x0000000105ba9b0b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x0000000105230141 objc_exception_throw + 48
2 CoreFoundation 0x0000000105ba9a59 -[NSException raise] + 9
3 Foundation 0x0000000104d4600b -[NSObject(NSKeyValueCoding) setValue:forKey:] + 292
4 UIKit 0x00000001075fc994 -[UIViewController setValue:forKey:] + 87
5 JJOC 0x00000001048e1206 -[JJKVCExceptionVC demoExceptionOne] + 54
6 JJOC 0x00000001048e11bd -[JJKVCExceptionVC viewDidLoad] + 189
7 UIKit 0x000000010760401a -[UIViewController loadViewIfRequired] + 1235
8 UIKit 0x0000000107642e6c -[UINavigationController _layoutViewController:] + 56
9 UIKit 0x000000010764374a -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 466
10 UIKit 0x00000001076438bb -[UINavigationController _startTransition:fromViewController:toViewController:] + 127
11 UIKit 0x0000000107644a03 -[UINavigationController _startDeferredTransitionIfNeeded:] + 843
12 UIKit 0x0000000107645b41 -[UINavigationController __viewWillLayoutSubviews] + 58
13 UIKit 0x000000010783760c -[UILayoutContainerView layoutSubviews] + 231
14 UIKit 0x000000010752455b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
15 QuartzCore 0x00000001070a3904 -[CALayer layoutSublayers] + 146
16 QuartzCore 0x0000000107097526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
17 QuartzCore 0x00000001070973a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
18 QuartzCore 0x0000000107026e92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
19 QuartzCore 0x0000000107053130 _ZN2CA11Transaction6commitEv + 468
20 QuartzCore 0x0000000107053b37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115
21 CoreFoundation 0x0000000105b4f717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
22 CoreFoundation 0x0000000105b4f687 __CFRunLoopDoObservers + 391
23 CoreFoundation 0x0000000105b34038 CFRunLoopRunSpecific + 440
24 UIKit 0x000000010745b08f -[UIApplication _run] + 468
25 UIKit 0x0000000107461134 UIApplicationMain + 159
26 JJOC 0x00000001048edcaf main + 111
27 libdyld.dylib 0x0000000109d9f65d start + 1
)
这个就是找不到key的崩溃,怎么处理这个异常呢?其实可以重写下面方法。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"找不到key的异常可以在这里处理");
}
在重写的方法里面可以处理找不到key的情况,但是实际上不这么做,应该尽量避免这种错误,直接复制粘贴用到的属性,不要手动敲,容易出错。
2. 传值为nil
这总错误不会崩溃,但是很隐蔽,不容易发现。
下面还是看代码
#import "JJKVCExceptionVC.h"
@interface JJKVCExceptionVC ()
@property (nonatomic, copy) NSString *name;
@end
@implementation JJKVCExceptionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self demoExceptionTwo];
}
#pragma mark - Object Private Function
//异常场景2:传值传nil
- (void)demoExceptionTwo
{
[self setValue:nil forKey:@"name"];
NSLog(@"name = %@", self.name);
}
@end
下面看输出结果
2017-09-09 11:53:36.333 JJOC[4101:99896] name = (null)
可见,你value即使传入的是nil,但是程序也不会崩溃,他会自动的给这个属性赋值为null。这里之所以不崩溃是因为我定义的属性name属于对象,对象可以有nil类型,但是如果我定义的属性是非对象的呢,下面看一下验证代码。
#import "JJKVCExceptionVC.h"
@interface JJKVCExceptionVC ()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation JJKVCExceptionVC
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self demoExceptionTwo];
}
#pragma mark - Object Private Function
//异常场景2:传值传nil
- (void)demoExceptionTwo
{
[self setValue:nil forKey:@"age"];
NSLog(@"name = %ld", self.age);
}
@end
这里age我定义的属性不是对象,而是一个整型,下面我们运行你就会发现这次不能运行了,崩溃并抛出异常了,如下所示:
2017-09-09 12:00:01.072 JJOC[4673:106219] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<JJKVCExceptionVC 0x7f92d6506440> setNilValueForKey]: could not set nil as the value for the key age.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010ced4b0b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010c55b141 objc_exception_throw + 48
2 CoreFoundation 0x000000010cf3d625 +[NSException raise:format:] + 197
3 Foundation 0x000000010c139884 -[NSObject(NSKeyValueCoding) setNilValueForKey:] + 81
4 Foundation 0x000000010c07100b -[NSObject(NSKeyValueCoding) setValue:forKey:] + 292
5 UIKit 0x000000010e927994 -[UIViewController setValue:forKey:] + 87
6 JJOC 0x000000010bc0c0f6 -[JJKVCExceptionVC demoExceptionTwo] + 54
7 JJOC 0x000000010bc0c0ad -[JJKVCExceptionVC viewDidLoad] + 189
8 UIKit 0x000000010e92f01a -[UIViewController loadViewIfRequired] + 1235
9 UIKit 0x000000010e96de6c -[UINavigationController _layoutViewController:] + 56
10 UIKit 0x000000010e96e74a -[UINavigationController _updateScrollViewFromViewController:toViewController:] + 466
11 UIKit 0x000000010e96e8bb -[UINavigationController _startTransition:fromViewController:toViewController:] + 127
12 UIKit 0x000000010e96fa03 -[UINavigationController _startDeferredTransitionIfNeeded:] + 843
13 UIKit 0x000000010e970b41 -[UINavigationController __viewWillLayoutSubviews] + 58
14 UIKit 0x000000010eb6260c -[UILayoutContainerView layoutSubviews] + 231
15 UIKit 0x000000010e84f55b -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1268
16 QuartzCore 0x000000010e3ce904 -[CALayer layoutSublayers] + 146
17 QuartzCore 0x000000010e3c2526 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 370
18 QuartzCore 0x000000010e3c23a0 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
19 QuartzCore 0x000000010e351e92 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 294
20 QuartzCore 0x000000010e37e130 _ZN2CA11Transaction6commitEv + 468
21 QuartzCore 0x000000010e37eb37 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 115
22 CoreFoundation 0x000000010ce7a717 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
23 CoreFoundation 0x000000010ce7a687 __CFRunLoopDoObservers + 391
24 CoreFoundation 0x000000010ce5f038 CFRunLoopRunSpecific + 440
25 UIKit 0x000000010e78608f -[UIApplication _run] + 468
26 UIKit 0x000000010e78c134 UIApplicationMain + 159
27 JJOC 0x000000010bc18c6f main + 111
28 libdyld.dylib 0x00000001110ca65d start + 1
)
这是为什么呢?因为name
属性是对象,所以赋值为nil不会崩溃,对象类型可以为nil;但是age
是整数,整数的类型不会是nil,这么强行赋值就会抛出异常出现错误。如果你不小心传了nil,KVC会调用setNilValueForKey:
方法。这个方法默认是抛出异常。
解决方法就是重写这个方法,在里面做一些额外的自定义处理。
- (void)setNilValueForKey:(NSString *)key
{
NSLog(@"属性值不能为nil");
}
重写了这个方法就不会崩溃和出错了,看输出结果。
2017-09-09 12:05:34.857 JJOC[4849:110183] 属性值不能为nil
2017-09-09 12:05:34.857 JJOC[4849:110183] name = 0
重写了以后可以顺利的输出了,整形默认值为0,所以这里age输出值为0。
后记
未完,待续~~~