KVC(Key-value coding)键值编码: 指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。(一个非正式的Protocol,提供一种机制来间接访问对象的属性。而不是通过调用Setter、Getter方法访问。)这样就可以在运行时动态在访问和修改对象的属性,而不是在编译时确定,这也是iOS开发中的黑魔法之一。
KVC最为重要的四个方法:
-(nullableid)valueForKey:(NSString *)key; //直接通过Key来取值
-(void)setValue:(nullableid)valueforKey:(NSString *)key; //通过Key来设值
-(nullableid)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
-(void)setValue:(nullableid)valueforKeyPath:(NSString *)keyPath; //通过KeyPath来设值
NSKeyValueCoding类别中其他的方法:
+(BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
-(BOOL)validateValue:(inoutid__nullable *__nonnull)ioValueforKey:(NSString *)inKeyerror:(outNSError **)outError;
//KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
-(NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
-(nullableid)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
-(void)setValue:(nullableid)valueforUndefinedKey:(NSString *)key;
//和上一个方法一样,只不过是设值。
-(void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
在KVC中使用KeyPath
然而在开发过程中,一个类的成员变量有可能是其他的自定义类,你可以先用KVC获取出来再该属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径KeyPath。
-(nullableid)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
-(void)setValue:(nullableid)valueforKeyPath:(NSString *)keyPath; //通过KeyPath来设值
例子:
@interfaceAddress:NSObject
@end
@interfaceAddress()
@property(nonatomic,copy)NSString*country;
@end
@implementationAddress
@end
@interfacePeople:NSObject
@end
@interfacePeople()
@property(nonatomic,copy)NSString*name;
@property(nonatomic,strong)Address*address;
@property(nonatomic,assign)NSIntegerage;
@end
@implementationPeople
@end
intmain(intargc,constchar*argv[]){
@autoreleasepool{
People*people1=[Peoplenew];
Address*add=[Addressnew];
add.country=@"China";
people1.address=add;
NSString*country1=people1.address.country;
NSString *country2=[people1valueForKeyPath:@"address.country"];
NSLog(@"country1:%@ country2:%@",country1,country2);
[people1setValue:@"USA"forKeyPath:@"address.country"];
country1=people1.address.country;
country2=[people1valueForKeyPath:@"address.country"];
NSLog(@"country1:%@ country2:%@",country1,country2);
}
return0;
}
//打印结果
2016-04-1715:55:22.487KVCDemo[1190:82636]country1:China country2:China
2016-04-1715:55:22.489KVCDemo[1190:82636]country1:USA country2:USA
上面的代码简单在展示了KeyPath是怎么用的。如果你不小心错误的使用了key而非KeyPath的话,KVC会直接查找address.country
这个属性,很明显,这个属性并不存在,所以会再调用UndefinedKey
相关方法。而KVC对于KeyPath是搜索机制第一步就是分离key,用小数点.
来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去。
KVC如何处理异常
KVC中最常见的异常就是不小心使用了错误的Key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。如果你不小心传了nil,KVC会调用setNilValueForKey:
方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
[people1setValue:nilforKey:@"age"]
***Terminatingappduetouncaughtexception'NSInvalidArgumentException',reason:'[ setNilValueForKey]: could not set nil as the value for the key age.'// 调用setNilValueForKey抛出异常
如果重写setNilValueForKey:
就没问题了
@implementationPeople
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"不能将%@设成nil",key);
}
@end
//打印出
2016-04-1716:19:55.298KVCDemo[1304:92472]不能将age设成nil
KVC处理非对象和自定义对象
不是每一个方法都返回对象,但是valueForKey:
总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开以者需要手动转换成原来的类型。尽管valueForKey:
会自动将值类型封装成对象,但是setValue:forKey:
却不行。你必须手动将值类型转换成NSNumber
或者NSValue
类型,才能传递过去。
对于自定义对象,KVC也会正确以设值和取值。因为传递进去和取出来的都是id类型,所以需要开发者自己担保类型的正确性,运行时Objective-C在发送消息的会检查类型,如果错误会直接抛出异常。
KVC与容器类
@interfacedemo:NSObject
@property(nonatomic,strong)NSMutableArray*arr;
@end
@implementationdemo
-(id)init{
if(self==[superinit]){
_arr=[NSMutableArraynew];
[selfaddObserver:selfforKeyPath:@"arr"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:nil];
}
returnself;
}
-(void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void*)context{
NSLog(@"%@",change);
}
-(void)dealloc{
[selfremoveObserver:selfforKeyPath:@"arr"];//一定要在dealloc里面移除观察
}
-(void)addItem{
[_arraddObject:@"1"];
}
-(void)addItemObserver{
[[selfmutableArrayValueForKey:@"arr"]addObject:@"1"];
}
-(void)removeItemObserver{
[[selfmutableArrayValueForKey:@"arr"]removeLastObject];
}
@end
然后再:
demo*d=[demonew];
[daddItem];
[daddItemObserver];
[dremoveItemObserver];
打印结果
2016-04-1817:48:22.675KVCDemo[32647:505864]{
indexes="[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind=2;
new= (
1
);
}
2016-04-1817:48:22.677KVCDemo[32647:505864]{
indexes="[number of indexes: 1 (in 1 ranges), indexes: (1)]";
kind=3;
old= (
1
);
}
从上面的代码可以看出,当只是普通地调用[_arr addObject:@"1"]
时,Observer并不会回调,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"]
;这样写时才能正确地触发KVO。打印出来的数据中,可以看出这次操作的详情,kind可能是指操作方法(我还不是很确认),old和new并不是成对出现的,当加添新数据时是new,删除数据时是old
而对于无序的容器,可以用下面的方法:
-(NSMutableSet *)mutableSetValueForKey:(NSString *)key; 同样,它们也有对应的keyPath版本-(NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
-(NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
KVC和字典
当对NSDictionary对象使用KVC时,valueForKey:
的表现行为和objectForKey:
一样。所以使用valueForKeyPath:
用来访问多层嵌套的字典是比较方便的。
KVC里面还有两个关于NSDictionary的方法
-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
-(void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
dictionaryWithValuesForKeys:
是指输入一组key,返回这组key对应的属性,再组成一个字典。setValuesForKeysWithDictionary
是用来修改Model中对应key的属性。下面直接用代码会更直观一点
Address*add=[Addressnew];
add.country=@"China";
add.province=@"Guang Dong";
add.city=@"Shen Zhen";
add.district=@"Nan Shan";
NSArray*arr=@[@"country",@"province",@"city",@"district"];
NSDictionary*dict=[adddictionaryWithValuesForKeys:arr];//把对应key所有的属性全部取出来
NSLog(@"%@",dict);
NSDictionary*modifyDict=@{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
[addsetValuesForKeysWithDictionary:modifyDict]; //用key Value来修改Model的属性
NSLog(@"country:%@ province:%@ city:%@",add.country,add.province,add.city);
//打印结果
2016-04-1911:54:30.846KVCDemo[6607:198900]{
city="Shen Zhen";
country=China;
district="Nan Shan";
province="Guang Dong";
}
2016-04-1911:54:30.847KVCDemo[6607:198900]country:USA province:californiacity:Losangle
KVC的使用
KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。下面我来列举iOS开发中KVC的使用场景
动态地取值和设值
利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,
用KVC来访问和修改私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的,请参考本文前面的Dog类的例子。
Model和字典转换
- -(id) initWithDictionary:(NSMutableDictionary*) jsonObject
- {
- if((self = [super init]))
- {
- [self init];
- [self setValuesForKeysWithDictionary:jsonObject];
- }
- return self;
- - (void)setValue:(id)value forUndefinedKey:(NSString *)key
- {
- if([key isEqualToString:@"nameXXX"])
- self.name = value;
- if([key isEqualToString:@"ageXXX"])
- self.age = value;
- else
- [super setValue:value forKey:key];
- }
修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些空间的API,这样我们就无法正常地访问和修改这些控件的样式。而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了。
下面演示如果修改placeHolder的文字样式。这里的关键点是如果获取你要修改的样式的属性名,也就是key或者keyPath名。
0 0