本文转自http://blog.csdn.net/ajrm0925/article/details/7416419
KVO(NSKeyValueObserving 的简称)、KVC(NSKeyValueCoding 的简称)
在很多时候接触到很多地方都有对 KVC,KVO 的描述,但是都是一笔带过。只知道这是Object-C提供的一个不错的机制,可以很好的减少代码。
首先我们先了解下 KVO 的机制,KVO:当指定的对象的属性被修改了,允许对象接收到通知的机制。每当在类中定义一个监听
如: [self addObserver:self forKeyPath:@"items" options:0 context:contexStr];
当然你还可以监听其他对象的属性变化,如:[person addObserver:money forKeyPath:@"account" options:0 context:contexStr];
只要当前类中 items 这个属性发生的变化都会触发到以下的方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
KVO 的优点:
当有属性改变,KVO 会提供自动的消息通知。这样开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。
这是 KVO 机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。
开发人员不需要添加任何代码,不需要设计自己的观察者模型,直接可以在工程里使用。
其次,KVO 的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。
KVC 的实现分析
KVC 运用了一个 isa-swizzling 技术。
isa-swizzling 就是类型混合指针机制。KVC 主要通过 isa-swizzling,来实现其内部查找定位的。
isa 指针,就是 is a kind of 的意思,指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针和其它数据。
如下 KVC 的代码:
[person setValue:@"personName" forKey:@"name"];
就会被编译器处理成:
SEL sel = sel_get_uid("setValue:forKey:");
IMP method = objc_msg_lookup (person->isa,sel);
method(person,sel,@"personName",@"name");
其中:
SEL数据类型:它是编译器运行Objective-C里的方法的环境参数。
IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。
KVC在调用方法setValue的时候
(1)首先根据方法名找到运行方法的时候所需要的环境参数。
(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。
(3)再直接查找得来的具体的方法实现。
这样的话前面介绍的KVO实现就好理解了
当一个对象注册了一个观察者,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。
所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名。
这样只有当我们调用KVC去访问key值的时候KVO才会起作用。所以肯定确定的是,KVO是基于KVC实现的。
下面实例演示一下:
@implementation Person
@synthesize name,age;//属性name将被监视
-(void)changeName
{
name=@"changeName directly";
}
@end
@implementation PersonMonitor
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqual:@"name"])
{
NSLog(@"change happen,old:%@ new:%@",[change objectForKey:NSKeyValueChangeOldKey],[change objectForKey:NSKeyValueChangeNewKey]);
}
}
@end
//测试代码
Person *p =[[Person alloc] init];//监视对象
PersonMonitor *pm= [[PersonMonitor alloc]init];
[p addObserver:pm forKeyPath:@"name" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:nil];
//测试前的数据
NSLog(@"p.name is %@",p.name);
//通过setvalue 的方法,PersonMonitor的监视将被调用
[p setValue:@"name kvc" forKey:@"name"];
//查看设置后的值
NSLog(@"p name get by kvc is %@",[p valueForKey:@"name"]);
//效果和通过setValue 是一致的
p.name=@"name change by .name=";
//通过person自己的函数来更改 name
[p changeName];
//最后一次修改是直接修改,所以没法产生通知。
下面转自http://steven.427.blog.163.com/blog/static/1023673802012723564165/
KVO
Key-Value Observing (简写为KVO):当指定的对象的属性被修改了,允许对象接受到通知的机制。每次指定的被观察对象的属性被修改的时候,KVO都会自动的去通知相应的观察 者。
KVO的优点
当有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。这 是KVO机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模 型,直接可以在工程里使用。其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。
2. 通过继承一个特定的方法,并且指定希望监视的对象及希望监视的属性名称,就能在该对象的指定属性的值发生改变时,得到一个“通知”(尽管这不是一个真正意 义上的通知),并且得到相关属性的值的变化(原先的值和改变后的新值)。
3. 通过一个简单的函数调用,使一个视图对象的一个指定属性随时随地都和一个控制器对象或模型对象的一个指定属性保持同步。
2.1 概述
KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。
当使用KVO、Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。
2.2 如何使用KVC
关键方法定义在:NSKeyValueCodingprotocol
KVC支持类对象和内建基本数据类型。
2.2.1 获取值
valueForKey:,传入NSString属性的名字。
valueForKeyPath:,传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。
2.2.2 修改值
setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:
setNilValueForKey: 当对非类对象属性设置nil时,调用,默认抛出异常。
2.2.3 一对多关系成员的情况
mutableArrayValueForKey:有序一对多关系成员 NSArray
mutableSetValueForKey:无序一对多关系成员 NSSet
示例:
2.3 KVC的实现细节
搜索Setter、Getter方法
这一部分比较重要,能让你了解到KVC调用之后,到底是怎样获取和设置类成员值的。
2.3.1 搜索简单的成员
如:基本类型成员,单个对象类型成员:NSInteger,NSString*成员。
a. setValue:forKey的搜索方式:
1. 首先搜索set<Key>:方法
如果成员用@property,@synthsize处理,因为@synthsize告诉编译器自动生成set<Key>:格式的setter方法,所以这种情况下会直接搜索到。
注意:这里的<Key>是指成员名,而且首字母大写。下同。
2. 上面的setter方法没有找到,如果类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)。
那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。
3. 如果找到设置成员的值,如果没有调用setValue:forUndefinedKey:。
b. valueForKey:的搜索方式:
1. 首先按get<Key>、<key>、is<Key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2. 上面的getter没有找到,查找countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法。
如果countOf<Key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<Key>、objectIn<Key>AtIndex:、<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。
3. 还没查到,那么查找countOf<Key>、enumeratorOf<Key>、memberOf<Key>:格式的方法。
如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<Key>、enumeratorOf<Key>、memberOf<Key>:组合的形式调用。
4. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,_is<Key>,<key>,is<key>的顺序直接搜索成员名。
5. 再没查到,调用valueForUndefinedKey:。
2.3.2 查找有序集合成员,比如NSMutableArray
mutableArrayValueForKey:搜索方式如下:
1. 搜索insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:或者insert<Key>:atIndexes、remove<Key>AtIndexes:格式的方法。
如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:、removeObjectFrom<Key>AtIndex:、insert<Key>:atIndexes、remove<Key>AtIndexes:组合的形式调用。还有两个可选实现的接口:replaceObjectIn<Key>AtIndex:withObject:、replace<Key>AtIndexes:with<Key>:。
2. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set<Key>:方法。
也就是说,mutableArrayValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
3. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableArray消息方法直接转交给这个成员处理。
4. 再找不到,调用setValue:forUndefinedKey:。
2.3.3 搜索无序集合成员,如:NSSet。
mutableSetValueForKey:搜索方式如下:
1. 搜索add<Key>Object:、remove<Key>Object:或者add<Key>:、remove<Key>:格式的方法,如果至少一个insert方法和至少一个remove方法找到,那么返回一个可以响应NSMutableSet所有方法的代理集合。那么发送给这个代理集合的NSMutableSet消息方法,以add<Key>Object:、remove<Key>Object:、add<Key>:、remove<Key>:组合的形式调用。还有两个可选实现的接口:intersect<Key>、set<Key>:。
2. 如果reciever是ManagedObejct,那么就不会继续搜索了。
3. 否则,搜索set<Key>:格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set<Key>:方法。也就是说,mutableSetValueForKey取出的代理集合修改后,用set<Key>:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。
4. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<key>,<key>的顺序直接搜索成员名。如果找到,那么发送的NSMutableSet消息方法直接转交给这个成员处理。
5. 再找不到,调用setValue:forUndefinedKey:。
KVC还提供了下面的功能
2.4 值的正确性核查
KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
实现核查方法
为如下格式:validate<Key>:error:
如:
- -(BOOL)validateName:(id *)ioValue error:(NSError **)outError
- {
- // The name must not be nil, and must be at least two characters long.
- if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {
- if (outError != NULL) {
- NSString *errorString = NSLocalizedStringFromTable(
- @"A Person's name must be at least two characters long", @"Person",
- @"validation: too short name error");
- NSDictionary *userInfoDict =
- [NSDictionary dictionaryWithObject:errorString
- forKey:NSLocalizedDescriptionKey];
- *outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN
- code:PERSON_INVALID_NAME_CODE
- userInfo:userInfoDict] autorelease];
- }
- return NO;
- }
- return YES;
- }
调用核查方法:
validateValue:forKey:error:,默认实现会搜索 validate<Key>:error:格式的核查方法,找到则调用,未找到默认返回YES。
注意其中的内存管理问题。
2.5 集合操作
集合操作通过对valueForKeyPath:传递参数来使用,一定要用在集合(如:array)上,否则产生运行时刻错误。其格式如下:
Left keypath部分:需要操作对象路径。
Collectionoperator部分:通过@符号确定使用的集合操作。
Rightkey path部分:需要进行集合操作的属性。
2.5.1 数据操作
@avg:平均值
@count:总数
@max:最大
@min:最小
@sum:总数
确保操作的属性为数字类型,否则运行时刻错误。
2.5.2 对象操作
针对数组的情况
@distinctUnionOfObjects:返回指定属性去重后的值的数组
@unionOfObjects:返回指定属性的值的数组,不去重
属性的值不能为空,否则产生异常。
2.5.3 数组操作
针对数组的数组情况
@distinctUnionOfArrays:返回指定属性去重后的值的数组
@unionOfArrays:返回指定属性的值的数组,不去重
@distinctUnionOfSets:同上,只是返回值为NSSet
2.6 效率问题
相比直接访问KVC的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。