我之前写过用点语法来设置,修改对象的属性,其实也是通过setter,getter的方法来设置和修改对象的属性。实际上,OC还支持一种更加灵活的操作方式,这种方式允许以字符串的形式间接操作对象的属性。这种方式就是今天我要介绍的KVC.
一、简单的KVC
最简单的KVC的操作属性有两个方法如下:
> setValue:属性值forKey:属性名:为指定属性设置值。
>valueForKey:属性名:获取指定属性的值
例如一下例子:
程序代码:Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
//使用@property定义3个property(属性)
@property(nonatomic,copy)NSString * name;
@property(nonatomic,copy)NSString * pass;
@property(nonatomic,copy)NSString * birth;
@end
下面在main函数中使用KVC来设置Dog对象的属性,以及访问Dog对象的属性,程序代码如下:
程序代码:main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Dog对象
Dog *dog = [[Dog alloc]init];
//使用KVC方式为name设置属性值
[dog setValue:@"黑马哥" forKey:@"name"];
//使用KVC方式为pass设置属性值
[dog setValue:@"222" forKey:@"pass"];
//使用KVC方式为birth设置属性值
[dog setValue:@"sdfs" forKey:@"birth"];
//使用KVC获取dog对象的属性
NSLog(@"dog的name是:%@",[dog valueForKey:@"name"]);
NSLog(@"dog的pass是:%@",[dog valueForKey:@"pass"]);
NSLog(@"dog的birth是:%@",[dog valueForKey:@"birth"]);
}
return 0;
}
<pre name="code" class="objc">这是使用KVC设置属性的值
[dog setValue:@"黑马哥" forKey:@"name"];
这是使用KVC获取指定属性的值。
<pre name="code" class="objc">NSLog(@"dog的name是:%@",[dog valueForKey:@"name"]);
输出为:
2016-02-10 14:18:53.392 点语法访问属性[944:42376] dog的name是:黑马哥
2016-02-10 14:18:53.393 点语法访问属性[944:42376] dog的pass是:222
2016-02-10 14:18:53.393 点语法访问属性[944:42376] dog的birth是:sdfs
Program ended with exit code: 0
在KVC变成模式中,无论调用setValue:forKey还是用valueForKey:方法,都是通过NSString对象来指定被操作属性的。
对于setValue:属性值 [email protected]“name”;代码,底层的执行机制如下:
- 首先程序优先考虑调用“setName:”方法,代码通过setter方法完成设置;
- 如果没有找到setName方法,KVC机制会搜索该类名为:_name的成员变量
- 如果没有_name,那么,KVC机制会继续寻找名为name的成员变量。
- 如果上面三条都没有找到,那么系统会执行该对象的setValue:forUndefinedKey:方法。
《默认的setValue:forUndefinedKey:就是应发一个异常,这个异常常常会导致程序因为异常而结束》
对于[email protected]"name"; 代码,底层的执行机制如下:
- 程序优先调用"name;"代码来获取该getter方法的返回值
- 如果没有name方法,那么寻找该类名为:_name的成员变量。
- 如果没有_name成员变量,就继续寻找,那么久寻找该类名为name的成员变量。
- 如果上面的三条都没有达到,那么系统会自动调用该对象的valueforUndefinedKey:方法。
《默认的valueforUndefinedKey:方法实现就是引发一个异常,这个异常将会导致程序因为异常而结束》 一下程序将对上方阐述的做一个简单的举例: 程序代码 Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
@package
NSString * name;
NSString *_name;
}
@end
程序代码 Dog.m
#import "Dog.h"
@implementation Dog
{
int age;
}
@end
程序代码 main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Dog对象
Dog *dog =[[Dog alloc]init];
//使用KVC方式为name属性赋值,KVC搜索顺序为:
//1、setName:方法;2、_name成员变量;3、name成员变量
[dog setValue:@"小狗" forKey:@"name"];
//访问name/_name两个成员变量的值
NSLog(@"dog->name:%@",dog->name);
NSLog(@"dog->_name:%@",dog->_name);
//使用KVC对age赋值,将会导致类实现部分定义的age成员变量被赋值
[dog setValue:[NSNumber numberWithInt:5] forKey:@"age"];
NSLog(@"dog的age:%@",[dog valueForKey:@"age"]);
}
return 0;
}
输出:
2016-02-10 16:00:21.689 点语法访问属性[1021:57028] dog->name:(null)
2016-02-10 16:00:21.690 点语法访问属性[1021:57028] dog->_name:小狗
2016-02-10 16:00:21.691 点语法访问属性[1021:57028] dog的age:5
Program ended with exit code: 0
可以从上方输出看出:KVC执行顺序
//1、setName:方法;2、_name成员变量;3、name成员变量
当设置name是,执行到_name时,直接识别并输出,但是name并没有输出值,输出的为null。
二、处理不存在的key 当我们用KVC进行操作属性时,这些属性可能并不存在—-即不存在对应得setter,getter方法,也不存在对应的成员变量,KVC将会自动调用setValue:forUndefinedKey:和valueForUndefineKey:方法,但系统默认实现这两个方法仅仅是引发异常,并没有进行特别的处理。例如以下程序:
程序代码:Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@end
程序代码:main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Dog对象
Dog *dog =[[Dog alloc]init];
[dog setValue:@"小狗" forKey:@"name"];
NSLog(@"dog的age:%@",[dog valueForKey:@"name"]);
}
return 0;
}
运行将会输出:<下方为输出的异常>
2016-02-10 16:12:58.446 点语法访问属性[1049:59283] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Dog 0x100206710> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
上面异常的信息为:程序尝试设置的name并不存在,因此,程序引发了NSUnknownKeyException异常,我们在Dog.m文件中重写方法: -(void)setValue:(id)value forUndefinedKey:(NSString *)key 代码如下:
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"您设置的key:%@并不存在",key);
NSLog(@"您设置的value为:%@",value);
}
再次运行程序,可以看到如下输出的程序代码:
2016-02-10 18:06:15.387 点语法访问属性[1179:72930] 您设置的key:name并不存在
2016-02-10 18:06:15.388 点语法访问属性[1179:72930] 您设置的value为:小狗
2016-02-10 18:06:15.389 点语法访问属性[1179:72930] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Dog 0x100114550> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
看上方程序依然引发异常,因为我们还用了valueForKey这个方法,因为不存在name,_name也不存在name方法,KVC机制会调用方法valueForUndefinedKey:,为了实现自定义行为,为了程序不蹦,我们在Dog.m文件中进行以下方法;
-(id)valueForUndefinedKey:(NSString *)key
{
NSLog(@"您尝试访问的key:%@并不存在",key);
return nil;
}
此时我们可以看到,当KVC操作并不存在的key时,KVC机制总是调用重写过得方法进行处理,通过这种处理机制,可以非常方便的定制自己的处理行为。
三、处理nil值
当我们调用KVC来设置对象的属性时,如果属性的类型是基本数据类型(如:int,float,double),且程序传入了对应类型的值,当用KVC设置值时会报错 程序模拟如下:
程序代码:Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
//使用@property定义两个属性
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int price;
@end
程序代码:main.m
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Dog对象
Dog *dog =[[Dog alloc]init];
//使用KVC方式尝试为name,price设置属性为nil
[dog setValue:nil forKey:@"name"];
[dog setValue:nil forKey:@"price"];
NSLog(@"dog的name为%@",[dog valueForKey:@"name"]);
NSLog(@"dog的price为%@",[dog valueForKey:@"price"]);
}
return 0;
}
运行以上程序会报错,报错信息为:
2016-02-10 18:28:50.763 点语法访问属性[1294:79054] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<Dog 0x100203d40> setNilValueForKey]: could not set nil as the value for the key price.'
NSInvalidArgumentException
从上方程序可以看出,程序引发了一个‘NSInvalidArgumentException’异常,这就是由于int类型的属性不能接受nil所导致的,上面这段提示信息其实是有程序中的setNilValueForKey:方法所产生的。也就是说,当程序尝试为某个属性设置nil时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。如果为了定制这个行为,我们可以重写这个方法。在Dog的类的实现部分增加以下代码就可以
-(void)setNilValueForKey:(NSString *)key
{
//如果尝试将key为price的属性设为nil
if([key isEqualToString:@"price"])
{
_price = 0;
}
else
{
//回调父类的setNilValueForKey,执行默认行为
[super setNilValueForKey:key];
}
}
输出为:
2016-02-10 18:37:20.098 点语法访问属性[1328:81680] dog的name为(null)
2016-02-10 18:37:20.100 点语法访问属性[1328:81680] dog的price为0
四、Key路径 kvc除了操作对象之外,还可操作对象的”复合属性“,所谓复合属性,就是Key路径,比如:Item对象里面包含一个Dog类型的dog属性,而dog对象又包含了name和price 属性,那么kvc可以通过dog.name、dog.price这种Key路径来支持操作Item对象的dog属性的name 、price属性。 KVC协议中有如下两种方法操作Key路径 >setValue:forKeyPath: 根据Key路径设置属性值 >valueForKeyPath:根据Key路径获取属性值
程序代码:Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
//使用@property定义两个属性
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)int price;
@end
程序代码:Item.h
//
// Item.h
// Created by lyx on 16/2/10.
// Copyright (c) 2016年 李云祥. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Item : NSObject
@property(nonatomic,strong)Dog *dog;
@property(nonatomic,assign)int amount;
@property(nonatomic,assign)int aollPrice;
@end
程序代码:main.m
#import <Foundation/Foundation.h>
#import "Item.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Item对象
Item *item = [[Item alloc]init];
//使用KVC方式为amount赋值
[item setValue:@"287" forKey:@"amount"];
[item setValue:[[Dog alloc]init] forKey:@"dog"];
//使用setValue:forKeyPath设置dog属性的name属性
[item setValue:@"键盘" forKeyPath:@"dog.name"];
[item setValue:@"1189" forKeyPath:@"dog.price"];
NSLog(@"%@个%@,总的为:%@",[item valueForKey:@"amount"],[item valueForKeyPath:@"dog.name"],[item valueForKey:@"aollPrice"]);
}
return 0;
}
下方是输出的值:
2016-02-10 19:42:58.143 点语法访问属性[1508:93608] 287个键盘,总的为:0
Program ended with exit code: 0
通过KVC操作对象的性能比通过setter,getter方法操作的性能更差,使用KVC变成的优势是更加简洁,更适合提炼一些通用性质的代码,由于KVC方式允许通过字符串的方式来操作对象的属性,这个字符串既可以是常量,也可以使变量,因此,具有极高的灵活性。