先了解成员变量、属性、实例变量
实例变量: class类进行实例化出来的对象为实例对象。
成员变量: 在{ }
中所声明的变量都是成员变量。
(实例变量是一种特殊的成员变量)
// { } 里的全部为成员变量
@interface Person : NSObject{
@public
NSString *myName; //成员
id hello; // id - > class
UIButton *btn; // Class类进行实例化的实例变量
int age;
}
// 属性
@property (nonatomic, copy) NSString * name;
属性:属性是与其他对象交互的变量,会生成默认的setter + getter
方法。
苹果早期的编译器是GCC
,后来发展到LLVM
。在LLVM
下,当属性没有匹配到实例变量的时,会自动创建一个带下划线的成员变量(_name)。
在Xcode4.4
版本之前@property
和@synthesize
的功能是独立分工的:
1.@property
的作用是:自动的生成成员变量setter/getter
方法的声明如代码:
@property int age; // 它的作用和下面两行代码的作用一致
- (void)setAge:(int)age;
- (int)age;
// 注意:属性名称不要加前缀下划线,否则生成的get/set方法中也会有下划线_
2.@synthesize
的作用是实现@property
定义的方法代码如:
@synthesize age
将@property
中定义的属性自动生成getter/setter
的实现方法而且默认访问成员变量age
,如果指定访问成员变量_age
的话代码如:
@synthesize age = _age;
注意:分类中添加的属性是不会自动生成setter和getter方法的,必须要手动添加。(通过runtime关联对象的方式)
KVC要知识要建立在这些知识基础上面。
KVC概念
一种可以通过名称或键间接访问对象属性的机制。
访问对象值的基本方法有setValue(_:forKey:)
和value(forKey:)
, setValue
设置由指定键标识的属性的值,value
返回由指定键标识的属性的值。因此,可以以一致的方式访问对象的所有属性。
默认实现依赖于通常由对象实现的访问器方法(或者在需要时直接访问实例变量)。
KVC调用过程官方文档
由于官方KVC的源码没有开源,最好的学习方式听过读官方文档,然后自己演练并猜测出KVC的逻辑。
KVC的API
@interface NSObject(NSKeyValueCoding)
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
@end
当然NSKeyValueCoding类别中还有其他的一些方法,下面列举一些
//默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性�验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
同时苹果对一些容器类比如NSArray或者NSSet等,KVC有着特殊的实现。建议有基础的或者英文好的开发者直接去看苹果的官方文档,相信你会对KVC的理解更上一个台阶。
KVC的调用过程
KVC调用过程调试准备
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
@public
NSString *name;
}
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
#pragma mark - get相关
- (NSString *)getName{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)isName {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)_name {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
@end
valueForKey: 调用过程
在实例中搜索第一个名称为get
在viewDidLoad调用:
Person *p = [[Person alloc] init];
[p setValue:@"name" forKey:@"name"];
NSLog(@"%@",[p valueForKey:@"name"]);
valueForKey
底层调用访问器方法优先级从上到下,如下图:
但如果我们声明的成员变量是这样的:
@interface Person : NSObject{
@public
NSString *name;
NSString *_name;
NSString *_isName;
NSString *isName;
}
@end
#import "Person.h"
@implementation Person
// 没有写getter方法
@end
给每个成员变量赋值:
Person *p = [[Person alloc] init];
p->_name = @"_name";
p->_isName = @"_isName";
p->name = @"name";
p->isName = @"isName";
NSLog(@"%@",[p valueForKey:@"name"]);
log读取name成员变量却不是"name
"字符串,而是"_name
"。其官方文档给出读取的优先级:
_name
> _isName
> name
> isName
这个特性是LLVM
提供给我们使用,我们也可以不使用它,只需要在Person.m
里重写一个方法设置为NO
(一定要提供getter否则会崩溃):
// 这个方法只针对所有的getter实现都没有的情况,去访问实例变量
+ (BOOL)accessInstanceVariablesDirectly {
return YES; // 允许访问成员变量。默认返回值YES
}
总体读取优先级:
Person *p = [[Person alloc] init];
[p setValue:@"name" forKey:@"name"];
[p valueForKey:@"name"];
1. - (NSString *)getName {...}
2. - (NSString *)name {...}
3. - (NSString *)isName {...}
4. - (NSString *)_name {...}
当accessInstanceVariablesDirectly返回为YES时
5.1 `_name` > `_isName` > `name` > `isName`
当accessInstanceVariablesDirectly返回为NO时
5.2 进入第六步
6. valueForUndefinedKey: 最后报错
如果访问成员变量失败,则会调用valueForUndefinedKey:
这将在默认情况下引发一个异常,但是NSObject的子类
可能提供特定于键的行为。
setValue:ForKey: 调用过程
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject{
@public
NSString *name;
}
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
#pragma mark - set相关
- (void)setName:(NSString *)name{
NSLog(@"%s",__func__);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s",__func__);
}
- (void)setIsName:(NSString *)isName{
NSLog(@"%s",__func__);
}
@end
在viewDidLoad调用
Person *p = [[Person alloc] init];
[p setValue:@"name" forKey:@"name"];
调用优先级:setName
> _setName
> setIsName
(当没有setName就会调用_setName...)
若都不实现setName
、_setName
、setIsName
并且accessInstanceVariablesDirectly
返回NO,则会导致崩溃;
若都不实现setName
、_setName
、setIsName
并且accessInstanceVariablesDirectly
返回YES,就回去找成员变量,按照这个顺序优先级去赋值:_name
> _isName
> name
> isName
setValue:forKey:的方法底层走向:
KVC
与KVO
的联系
看看使用KVC改变属性的值是否能监听到
在Person.h
里添加一个属性,不重写getter/setter
@property (nonatomic, copy) NSString *subject;
在viewDidLoad里边添加属性观察后,使用KVC设置
Person *p = [[Person alloc] init];
// 这里解释一下,这里不添加kvc也是可以的,道理都是一样的; 添加了kvc,只是这个Person不再是Person了,而是NSNotifying_Person这个派生类,这里里面走的setkey和_setkey方法都是走的新类的,如果新类没有就走父类的,也就是Person的。
[p addObserver:self forKeyPath:@"subject" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context: @"sdf"];
[p setValue:@"test" forKey:@"subject"];
运行发现,使用KVC赋值也是能监听到
不看看出kvc内部就是实现了调用kvo的两个监听的方法:
[p willChangeValueForKey:@"subject"];
p->subject = @"test";
[p didChangeValueForKey:@"subject"];
自定义KVC
根据文档猜测KVC的调用流程:
新建NSObject的分类NSObject+WJKVC
NSObject+WJKVC.h:
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (WJKVC)
- (void)wj_setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)wj_valueForKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
NSObject+WJKVC.m:
#import "NSObject+WJKVC.h"
#import
@implementation NSObject (WJKVC)
- (void)wj_setValue:(nullable id)value forKey:(NSString *)key{
// 源码没有开源
// 猜测 -- 不安全
// 1:刷选key 判断非空
if (key == nil || key.length == 0) {
return;
}
// 2:找到相关方法 set _set setIs
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3:判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"WJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相关实例变量进行赋值
// 4.1 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _ _is is
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5:如果找不到相关实例
@throw [NSException exceptionWithName:@"WJUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
- (nullable id)wj_valueForKey:(NSString *)key{
// 1:刷选key 判断非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相关方法 get countOf objectInAtIndex
// key 要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i _is is
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
#pragma mark - 相关方法
- (BOOL)performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i
使用方法就和系统的一样。
KVC与容器类(数组、集合、字典)
对象的属性可以是一对一的,也可以是一对多的。一对多的属性要么是有序的(数组)
,要么是无序的(集合)
。
不可变的有序容器属性(NSArray
)和无序容器属性(NSSet
)一般可以使用valueForKey:
来获取。
比如Person
有一个叫items
的NSArray
属性,你可以用[person valurForKey: @"items"]
来获取这个属性。
NSMutableArray
当对象的属性是可变的容器时,对于有序的容器(NSMutableArray
),
赋值操作可以用下面的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
如果调用该方法,赋值操作流程如下:
搜索
insertObject:in
或者AtIndex:, removeObjectFrom AtIndex: insert
格式的方法,如果至少找到一个AdIndexes, remove AtIndexes insert
方法和一个remove
方法,那么同样返回一个可以响应NSMutableArray
所有方法代理集合(类名是NSKeyValueFastMutableArray2
),那么给这个代理集合发送NSMutableArray
的方法,以insertObject:in
或者AtIndex:, removeObjectFrom AtIndex: insert
组合的形式调用。还有两个可选实现的接口:AdIndexes, remove AtIndexes replaceObjectAtIndex:withObject:, replace
AtIndexes:with : 如果上步的方法没有找到,则搜索
set
格式的方法,如果找到,那么发送给代理集合的: NSMutableArray
最终都会调用set
方法。 也就是说,: mutableArrayValueForKey:
取出的代理集合修改后,用set
重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。: 如果上一步的方法还没有找到,再检查类方法
accessInstanceVariablesDirectly
,如果返回YES
(默认行为),会按_
的顺序搜索成员变量名,如果找到,那么发送的, , NSMutableArray
消息方法直接交给这个成员变量处理。如果还是找不到,则调
valueForUndefinedKey:
。
关于mutableArrayValueForKey:
的适用场景:
一般是用在对NSMutableArray
添加Observer上。如果对象属性是个NSMutableArray、NSMutableSet、NSMutableDictionary
等集合类型时,你给它添加KVO时,你会发现当你添加或者移除元素时并不能接收到变化。
因为KVO的本质是系统监测到某个属性的内存地址或常量改变时,会添加上- (void)willChangeValueForKey:(NSString *)key和- (void)didChangeValueForKey:(NSString *)key方法
来发送通知。
所以一种解决方法是手动调用者两个方法,但是并不推荐,你永远无法像系统一样真正知道这个元素什么时候被改变。另一种便是利用使用mutableArrayValueForKey:
了。
取值操作流程:
- 在实例中搜索第一个名称为
get
的访问器方法,顺序如下。如果找到了,调用它, ,是 ,或_< Key> - 如果没有找到简单的访问器方法,搜索名称匹配模式
countOf
和objectIn
(对应于AtIndex: NSArray
类定义的基本方法)和
(对应于NSArray方法objectsAtIndexes:)的方法实例。AtIndexes:
如果发现其中的第一个和至少另外两个中的一个,创建一个集合代理对象来响应所有的NSArray方法并返回它。
代理对象随后将它接收到的任何NSArray消息转换为countOf
,objectIn
和AtIndex:
消息的组合,并将其转换为创建它的符合键值编码的对象。AtIndexes: - 如果没有找到简单的访问方法或数组访问方法组,则查找名为
countOf
的三个方法(对应于、enumeratorOf 和memberOf : NSSet
类定义的基本方法)。
如果找到了所有三个方法,则创建一个集合代理对象来响应所有NSSet方法并返回该对象。
该代理对象随后将它接收到的任何NSSet消息转换为countOf
和、enumeratorOf memberOf
消息的某种组合,以发送给创建它的对象。: - 如果没有找到简单的访问方法或集合访问方法组,并且如果接收方的类方法
accessInstanceVariablesDirectly
返回YES,搜索名为_
的实例变量, _is< key>, ,或 - 如果检索到的属性值是一个对象指针,只需返回结果。如果该值是NSNumber支持的标量类型,将其存储在NSNumber实例中并返回。如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回。
- 如果所有其他方法都失败,调用
valueForUndefinedKey:
。这将在默认情况下引发一个异常,但是NSObject的子类可能提供特定于键的行为。
取值操作可以用下面的方法:
在Person类里定义一个NSMutableArray属性
#import
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) NSMutableArray *penArr;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
@implementation Person
// 个数
- (NSUInteger)countOfPens {
return [self.penArr count];
}
// 迭代器
-(id)enumeratorOfPens {
// objectEnumerator
return [self.penArr reverseObjectEnumerator];
}
// 是否包含这个成员对象
- (id)memberOfPens:(id)object {
return [self.penArr containsObject:object] ? object : nil;
}
// 获取值
- (id) objectInPensAtIndex:(NSUInteger)index {
return [NSString stringWithFormat:@"pens %lu", index];
}
@end
在viewDidLoad:(注意:Person里并没有pens属性)
Person *p = [Person new];
p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
NSArray *arr = [p valueForKey:@"pens"]; // 动态成员变量
NSLog(@"pens = %@", arr);
打印结果:
也可以使用NSSet去接收pens:
NSSet *sets = [p valueForKey:@"pens"];
//遍历
NSEnumerator *e = [sets objectEnumerator];
NSString *str = nil;
while(str = [e nextObject]) {
NSLog(@"%@", str);
}
为什么可以这样?请看认真看取值过程滴2、3步。
NSMutableArray使用KVC时,KVO的监控值变化:
@interface Test : NSObject
@property (nonatomic,strong) NSMutableArray* arr;
@end
@implementation Test
-(id)init{
if (self == [super init]){
_arr = [NSMutableArray new];
[self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
return self;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
}
-(void)addObject:(id)object {
[_arr addObject:object];
}
-(void)addObjectObserver:(id)object {
[[self mutableArrayValueForKey:@"arr"] addObject:object];
}
-(void)removeObjectObserver{
[[self mutableArrayValueForKey:@"arr"] removeLastObject];
}
-(void)dealloc{
[self removeObserver:self forKeyPath:@"arr"]; //一定要在dealloc里面移除观察
}
@end
调用
Test* t = [Test new];
[t addObject: @"test1"];
[t addObjectObserver: @"test2"];
[t removeObjectObserver];
打印结果
从上面结果看出:普通数组的addObject:
时,Observer不会回调,只有mutableArrayValueForKey: addObject:
才会触发KVO。
NSMutableSet
对于无序的容器(NSMutableSet
),可以用下面的方法:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
返回一个可变的无序数组如果调用该方法,KVC的搜索顺序如下:
- 搜索
addObject
格式的方法,如果至少找到一个Object:, remove Object:或者 add , remove insert
方法和一个remove
方法,那么同样返回一个可以响应NSMutableSet
所有方法代理集合(类名是NSKeyValueFastMutableSet2
),那么给这个代理集合发送NSMutableSet
的方法,以addObject
组合的形式调用。还有两个可选实现的接口:Object:, remove Object:或者 add , remove intersect
。, set : - 如果
receiver
是ManagedObject
,那么就不会继续搜索。 - 如果上一步的方法没有找到,则搜索
set
格式的方法,如果找到,那么发送给代理集合的: NSMutableSet
最终都会调用set
方法。 也就是说,: mutableSetValueForKey
取出的代理集合修改后,用set
重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。: - 如果上一步的方法还没有找到,再检查类方法
accessInstanceVariablesDirectly
,如果返回YES(默认行为)
,会按_
的顺序搜索成员变量名,如果找到,那么发送的, NSMutableSet
消息方法直接交给这个成员变量处理。 - 如果还是找不到,调用
valueForUndefinedKey:
可见,除了检查receiver
是ManagedObject
以外,其搜索顺序和mutableArrayValueForKey
基本一致。
它们也有对应的keyPath
版本
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
NSDictionary
当对NSDictionary
对象使用KVC时,valueForKey:
的和objectForKey:
一样表现行为。所以使用valueForKeyPath:
用来访问多层嵌套的字典是比较方便的。
可以使用下面两个方法进行取值赋值:
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
字典模型之间的转换:
- (void)dictionaryAndModel {
NSDictionary* dict = @{@"name":@"steven", @"nick":@"st", @"subject":@"iOS", @"age":@18, @"height":@175};
Person *p = [[Person alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@",p);
// 键数组转模型到字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
}
KVC如何处理异常
KVC中最常见的异常就是不小心使用了错误的key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。
通常情况下,KVC不允许你要在调用setValue:属性值 forKey:@”name“(或者keyPath)
时对非对象传递一个nil
的值。很简单,因为值类型
是不能为nil
的。如果你不小心传了,KVC会调用setNilValueForKey:
方法。
[person setValue:nil forKey:@"age"]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[ setNilValueForKey]: could not set nil as the value for the key age.' // 调用setNilValueForKey抛出异常
如果重写setNilValueForKey:就没问题了
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"不能将%@设成nil",key);
}
KVC的正确性验证
KVC提供了属性值,用来验证key对应的Value是否可用的方法:
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
这个方法的默认实现是去探索类里面是否有一个这样的方法:-(BOOL)validate
如果有这个方法,就调用这个方法来返回,没有的话就直接返回YES
。