1、KVC的概念
OC支持一种更灵活的操作方式,这种方式允许以字符串形式间接操作对象的属性,这种方式的全称是Key Value Coding(KVC),即键值编码
KVC由NSKeyValueCoding提供支持,最基本的操作属性的两个方法如下:
-setValue:forKey: 为指定的属性设置值
-valueForKey: 获取指定属性的值
无论调用setValue:forKey方法,还是调用valueForKey:方法,都是通过NSString对象来指定操作的属性值,其中forKey参数值用于传入属性名
@interface StudentInformation : NSObject
@property (nonatomic,copy) NSString *stuName;
@property (nonatomic,assign) int stuAge;
@property (nonatomic,copy) NSString *stuProfession;
@property (nonatomic,assign) BOOL stuSex;
@end
StudentInformation *stu = [[StudentInformation alloc] init];
//设置属性值
[stu setValue:@"张三" forKey:@"stuName"];
// @18 相当于 [NSNumber numberWithInt:18];
[stu setValue:@18 forKey:@"stuAge"];
[stu setValue:@"数学" forKey:@"stuProfession"];
[stu setValue:@YES forKey:@"stuSex"];
//获取属性值
NSLog(@"name = %@,age = %@,profession = %@,sex = %@",[stu valueForKey:@"stuName"],[stu valueForKey:@"stuAge"],[stu valueForKey:@"stuProfession"],[[stu valueForKey:@"stuSex"] boolValue]?@"男":@"女");
//- (id)valueForKey:(NSString *)key得到的是一个id类型的数据
// int age = [[stu valueForKey:@"stuAge"] intValue];
// NSNumber *number = [stu valueForKey:@"stuAge"]; int age = [number intValue];
2、setValue:forKey:方法的执行机制(1)程序优先调用"setUserName:属性值"代码通过setter方法设置值
(2)如果该类没有setUserName:方法,那么KVC机制会搜索该类中名为_userName的成员变量,无论该成员变量是在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰。这条KVC代码底层实际上就是对_userName成员变量赋值
(3)如果该类既没有setUserName:方法,也没有定义_userName成员变量,那么KVC机制会搜索该类中名为userName的成员变量,无论该成员变量是在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰。这条KVC代码底层实际上就是对userName成员变量赋值
(4)如果上面3步都没有找到,那么系统将会执行该对象的setValue:forUndefinedKey:方法,实际上这个方法就是抛出一个异常,这个异常将会导致程序因为异常结束
3、valueForKey:方法执行机制
(1)程序优先考虑调用"userName"代码通过getter方法获取值
(2)如果该类没有userName方法,那么KVC机制会搜索该类中名为_userName的成员变量,无论该成员变量是在类接口中部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰。这条KVC代码底层实际上就是返回_userName成员变量的值
(3)如果该类中既没有userName方法,也没有定义_userName成员变量,那么KVC机制会搜索该类中名为userName的成员变量,无论该成员变量是在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰。这条KVC代码底层实际上就是返回userName成员变量的值
(4)如果上面3步都没有找到,那么系统将会执行该对象的valueForUndefinedKey:方法,实际上这个方法就是抛出一个异常,这个异常将会导致程序因为异常结束
4、处理不存在的key
重写两个方法处理不存在的key,使程序不会因为抛出异常而导致程序结束
-(void)setVlaue:(id)value forUndefinedKey:(NSString *)key;
-(id)valueForUndefinedKey:(NSString *)key;
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"给%@赋值时,%@未找到",key,key);
}
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"取%@的值时,%@未找到",key,key);
return @"test";
}
[stu setValue:@88 forKey:@"stuScore"];
NSLog(@"%@",[stu valueForKey:@"stuScore"]);
5、处理nil值
当调用KVC来设置对象的属性时,如果属性的类型是基本数据类型(如:int、float、double),且程序传入了对应类型的值,那么程序可以正确的设置。如果对基本数据类型的属性设置一个nil,则会报错,程序会引发一个NSInvalidArgumentException异常,这是由于基本数据类型不能接受nil值所导致的。
要使程序处理nil值,则要重写setNilValueForKey:方法来实现。
-(void)setNilValueForKey:(NSString *)key{
if ([key isEqualToString:@"stuAge"]) {//_stuAge是int类型的
_stuAge = 0;
}else if([key isEqualToString:@"stuSex"]){//_stuSex是BOOL类型的
_stuSex = NO;
}else{
[super setNilValueForKey:key];
}
}
[stu setValue:nil forKey:@"stuAge"];
[stu setValue:nil forKey:@"stuName"];
NSLog(@"%@,%@",[stu valueForKey:@"stuAge"],[stu valueForKey:@"stuName"]);
KVC除了可操作对象的属性之外,还可以操作对象的复合属性,所谓复合属性,KVC将其称为key路径。比如,Person对象包含一个IdCard类型的ic属性,而IdCard对象又包含了cardNo、cardName属性,那么KVC可以通过ic.cardNo、ic.cardName这种key路径来支持Person对象的ic属性中的cardNo、cardName属性。
在KVC中操作key路径的方法如下:
-setValue:forKeyPath: 为指定属性设置值
-valueForKeyPath: 获取指定属性的值
例如:在上面例子中添加一个属性
@property (nonatomic, strong) Dog *dog;
@interface Dog : NSObject
@property (nonatomic, copy) NSString *dogName;
@property (nonatomic,assign) int dogAge;
@property (nonatomic, copy) NSString *dogBrand;
@end
Dog *dog = [Dog new];
[stu setValue:dog forKey:@"dog"];
[stu setValue:@"小黑" forKeyPath:@"dog.dogName"];
NSLog(@"%@",[stu valueForKeyPath:@"dog.dogName"]);
7、KVO键值监听
利用KVO(Key Value Observing,键值监听)机制,当指定对象的属性被修改后,则对象就会收到接受通知。每次指定的被观察者的对象的属性被修改后,KVO就会自动通知相应的观察者。
KVO是观察者设计模式中的一种应用。KVO由NSKeyValueObserving提供支持,常用方法如下:
addObserver:forKeyPath:options:context:注册一个监听器用于监听指定的key路径
observerVlaueForKeyPath:ofObject:change:context:当被监听器的key路径对应的属性值发生改变时,就会回调监听器自身的方法。
removeObserver:forKeyPath:为key路径删除指定的监听器
@interface ShopCar : NSObject
@property (nonatomic, assign) int totalNumber;
@property (nonatomic, assign) float totalPrice;
@end
_shopCar = [[ShopCar alloc] init];
_shopCar.totalNumber = 0;
_shopCar.totalPrice = 0.0f;
_lbNumber.text = [NSString stringWithFormat:@"%i",_shopCar.totalNumber];
_lbPrice.text = [NSString stringWithFormat:@"%f",_shopCar.totalPrice];
//使用KVO添加监听
[_shopCar addObserver:self forKeyPath:@"totalNumber" options:NSKeyValueObservingOptionNew context:nil];
[_shopCar addObserver:self forKeyPath:@"totalPrice" options:NSKeyValueObservingOptionNew context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
//NSLog(@"%@",change);
if ([keyPath isEqualToString:@"totalNumber"]) {
_lbNumber.text = [NSString stringWithFormat:@"%i",[[change objectForKey:@"new"] intValue]];
}else if ([keyPath isEqualToString:@"totalPrice"]){
_lbPrice.text = [NSString stringWithFormat:@"%.2f",[[change objectForKey:@"new"] floatValue]];
}
}
- (IBAction)addAction:(id)sender {
_shopCar.totalNumber++;
_shopCar.totalPrice += 100;
}
- (IBAction)removeAction:(id)sender {
if (_shopCar.totalNumber > 0) {
_shopCar.totalNumber--;
_shopCar.totalPrice -= 100;
}else{
_shopCar.totalNumber = 0;
_shopCar.totalPrice = 0.0f;
}
}