一、键值编码 Key-Value-Coding
KVC是通过字符串的形式操作对象的属性,这个字符串既可以是常量也可以是变量,使用KVC可以使得编码更加简洁。
KVC支持类对象和内建基本数据类型。
获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。
修改值
setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:
setNilValueForKey:当对非类对象属性设置nil时,调用,默认抛出异常。
一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
在获取值或者修改值的时候可以使用valueForKey和setValue:forKey,都是通过NSString对象来指定被操作属性的,通过forKey传入属性名。而寻找属性名也是有顺序的,
setValue:forKey和valueforKey寻找key的方式按如下优先级进行:
1.首先会优先调用set方法,如果有自定义的setter方法,首先会调用自定义的set方法,没有自定义的会找@property合成的。
2.如果没有1中提到的setter方法,则KVC会搜索该类的(_成员变量,例如_name)成员变量,无论该成员变量在类接口部分还是在类实现部分,也不会考虑那个访问控制符。
3.如果如果没有setter,也没有(_成员变量),则会搜索该类名为(成员变量,name)的成员变量,无论该成员变量在类接口部分还是在类实现部分,也不会考虑那个访问控制符。
我们在使用setValue:forKey和valueforKey会遇到key不存在,或者为值赋值为nil的情况,那么我们需要重写 setValue:forUndefinedKey、 valueForUndefinedKey 、setNilValueForKey等方法。
下面是KVC对上面所讲内容的测试。
#import <Foundation/Foundation.h>
@interface Person : NSObject{
//声明成员变量,并没有指定setter和getter
NSDate* _birth;
}
//使用@property定义的属性
@property(nonatomic,copy) NSString* name;
//修改setter方法
@property(nonatomic,copy) NSString* sex;
//sex的setter方法
-(void)setSex:(NSString *)sex;
//重写setValue:forUndefinedKey,如果setter方法无果,则会调用这个方法,处理不存在的key
-(void)setValue:(id)value forUndefinedKey:(NSString *)key;
//重写valueForUndefinedKey,如果getter方法无果,则会调用此方法
-(id)valueForUndefinedKey:(NSString *)key;
//重写setNilValueForKey,处理nil值
-(void)setNilValueForKey:(NSString *)key;
@end
#import "Person.h"
@implementation Person{
//类实现部分的属性,无setter无getter
int age;
}
-(void)setSex:(NSString *)sex{
self->_sex = [NSString stringWithFormat:@"--%@--",sex];
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"值--%@--不存在",key);
}
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"你要获取的值%@并不存在",key);
return nil;
}
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"为%@赋值nil",key);
//如果name的属性值赋为nil则修改name的值
if ([key isEqualToString:@"age"]) {
NSLog(@"要为age赋值为nil,执行默认赋值0");
self->_name = 0;
}else{
//回掉父类的setNilValueForKey
[super setNilValueForKey:key];
}
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建Person对象
Person* person = [[Person alloc]init];
//使用KVC为@property声明的属性赋值
[person setValue:@"小明" forKey:@"name"];
//使用kvc获取name属性值
NSLog(@"name: %@",[person valueForKey:@"name"]);
//使用KVC为@property声明并自定义setter方法的属性赋值
[person setValue:@"男" forKey:@"sex"];
//使用kvc获取sex属性值
NSLog(@"sex: %@",[person valueForKey:@"sex"]);
//使用KVC为没有定义setter和getter方法的成员变量赋值
[person setValue:[[NSDate alloc]init] forKey:@"birth"];
//使用kvc获取birth属性值
NSLog(@"birth: %@",[person valueForKey:@"birth"]);
//使用KVC为在实现部分定义的属性age赋值
[person setValue:[NSNumber numberWithInt:22] forKey:@"age"];
//使用kvc获取age属性值
NSLog(@"age: %@",[person valueForKey:@"age"]);
//处理不存在的key
[person setValue:@"小兰" forKey:@"grilFriend"];
[person valueForKey:@"grilFriend"];
//处理nil值
[person setValue:nil forKey:@"age"];
}
return 0;
}
输出结果:
2014-12-07 17:49:25.442 Blog_02_OC[2033:108207] name:
小明
2014-12-07 17:49:25.443 Blog_02_OC[2033:108207] sex: -- 男 --
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] birth: 2014-12-07 09:49:25 +0000
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] age: 22
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] 值 --grilFriend-- 不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 你要获取的值 grilFriend 并不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 为 age 赋值 nil
2014-12-07 17:49:25.443 Blog_02_OC[2033:108207] sex: -- 男 --
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] birth: 2014-12-07 09:49:25 +0000
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] age: 22
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] 值 --grilFriend-- 不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 你要获取的值 grilFriend 并不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 为 age 赋值 nil
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 要为age赋值为nil,执行默认赋值0
二、键值监听Key-Value-Observer
KVO机制NSKeyValueObserving协议提供支持,有以下几种方法:
addObserver:forKeyPath:options:context:注册一个监听器用于监听指定的Key路径。
removeObserver:forKeyPath:为key路径删除指定的监听器。
removeObserver:forKeyPath:context:为Key路径删除指定的监听器。
key所指定的值改变后会回掉监听器自身的监听方法,监听方法如下:
observerValueForKeyPath:ofObject:change:context。重写该方法就可以得到修改的数据。
下面是用一个例子来看下键值监听的用途,我们首先定义一个Books类,这个类里面有两个属性,分别是书名和数目,我们再定义一个BookManger类,这个类会使用到Book,可以把BookManger类当作是一个视图,而这个视图会监听数据Book的变化,数据变化相应的界面也会变化,这个在IOS开发中会比较常用,我们使用main函数模拟值的变化。
Books类
#import <Foundation/Foundation.h>
@interface Books : NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int num;
@end
#import "Books.h"
@implementation Books
@end
BookManger类
#import <Foundation/Foundation.h>
@class Books;
@interface BookManger : NSObject
@property(nonatomic,weak) Books* myBook;
-(void)printBooks;
@end
#import "BookManger.h"
#import "Books.h"
@implementation BookManger
@synthesize myBook=_myBook;
-(void)printBooks{
NSLog(@"书名%@,数量%d",_myBook.name,_myBook.num);
}
//自定义myBook的setter方法
-(void)setMyBook:(Books *)myBook{
self->_myBook = myBook;
//为myBook的name属性添加监听
[self.myBook addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//为myBook的num属性添加监听
[self.myBook addObserver:self forKeyPath:@"num" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"被修改的keyPath为:%@",keyPath);
NSLog(@"被修改的对象为:%@",object);
NSLog(@"被修改的属性值为:%@",change);
NSLog(@"被修改的上下文为:%@",context);
}
-(void)dealloc{
[self.myBook removeObserver:self forKeyPath:@"name"];
[self.myBook removeObserver:self forKeyPath:@"num"];
}
@end
main函数
#import <Foundation/Foundation.h>
#import "Books.h"
#import "BookManger.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Books* book = [[Books alloc]init];
book.name = @"OC开发";
book.num = 10;
BookManger* bookManger = [[BookManger alloc]init];
bookManger.myBook = book;
//打印初始的书信息
[bookManger printBooks];
//修改book的name属性
book.name = @"IOS学习";
//修改book的num属性
book.num = 20;
}
}
运行程序,可以看到已经成功的监听到了值的变化:
2014-12-07 22:11:39.923 Blog_02_OC[2951:166400]
书名
OC
开发,数量
10
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的 keyPath 为 :name
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的属性值为 :IOS 学习
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的上下文为 :(null)
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的 keyPath 为 :num
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的属性值为 :20
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的 keyPath 为 :name
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的属性值为 :IOS 学习
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400] 被修改的上下文为 :(null)
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的 keyPath 为 :num
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的属性值为 :20
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的上下文为:(null)