分享

KVC,KVO的一些原理理解与使用介绍

 最初九月雪 2015-04-20

使用KVC、KVO的优势

通过规定了一组通用的Cocoa命名法则、调用规则等,实现了如下功能: 

2  使用一对高度规范化的访问方法,获取以及设置任何对象的任何属性的值。

2  通过继承一个特定的方法,并且指定希望监视的对象及希望监视的属性名称,就能在该对象的指定属性的值发生改变时,得到一个“通知”(尽管这不是一个真正意 义上的通知),并且得到相关属性的值的变化(原先的值和改变后的新值)。

2  通过一个简单的函数调用,使一个视图对象的一个指定属性随时随地都和一个控制器对象或模型对象的一个指定属性保持同步。

2.    KVC

2.1  概述

KVC是KeyValue Coding的简称,它是一种可以直接通过字符串的名字(key)来访问类属性的机制。而不是通过调用Setter、Getter方法访问。

当使用KVO、Core Data、CocoaBindings、AppleScript(Mac支持)时,KVC是关键技术。

 

2.2  如何使用KVC

关键方法定义在:NSKeyValueCoding(Dick标注:注意你可以在代码里通过这个关键词跳转到源代码的头文件里看下就明白了,他是NSMutableArray,dictionary等等类的扩展方法的扩展名。看了源代码后再结合下面的转载内容你就更容易明白了下面说的是什么)

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:

如:

  1. -(BOOL)validateName:(id *)ioValue error:(NSError **)outError  
  2. {  
  3.     // The name must not be nil, and must be at least two characters long.  
  4.     if ((*ioValue == nil) || ([(NSString *)*ioValue length] < 2]) {  
  5.         if (outError != NULL) {  
  6.             NSString *errorString = NSLocalizedStringFromTable(  
  7.                     @"A Person's name must be at least two characters long", @"Person",  
  8.                     @"validation: too short name error");  
  9.             NSDictionary *userInfoDict =  
  10.                 [NSDictionary dictionaryWithObject:errorString  
  11.                                             forKey:NSLocalizedDescriptionKey];  
  12.             *outError = [[[NSError alloc] initWithDomain:PERSON_ERROR_DOMAIN  
  13.                                                     code:PERSON_INVALID_NAME_CODE  
  14.                                                 userInfo:userInfoDict] autorelease];  
  15.         }  
  16.         return NO;  
  17.     }  
  18.     return YES;  
  19. }  


调用核查方法: 

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的效率会稍低一点,所以只有当你非常需要它提供的可扩展性时才使用它。




====================以上比较细节所以都是转载过来了========================


而KVO的使用相对较简单:

  1. - (void) observeValueForKeyPath: (NSString *) keyPath  
  2.                        ofObject: (id) object  
  3.                          change: (NSDictionary *) change  
  4.                         context: (void *) context  
  5. {  
  6.         DLog(@"%@\n%@\n%@\n%@\n%@\n%d", keyPath, object, change, context, [change objectForKey:NSKeyValueChangeNewKey],isSelected);  
  7.     if([changeobjectForKey:NSKeyValueChangeNewKey] != [NSNullnull]) {           //注意这里的  
  8. NSKeyValueChangeNewKey,这个是用来取出改变后的新值,实际上还可以取出旧值,NSKeyValueChangeOldKey,更多的自己到xcode里跳转过去看下头文件  
  9.           DLog(@"refresh user location");  
  10.           [self refresh];   
  11.      }  
  12. }  


注册的时候调用如下代码,这样就完成对parserDatas的isFinished相关的key进行监控,触发后回调对象是self
[parserDatas addObserver:self forKeyPath:@"isFinished"    options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:parserDatas];

回调方法如下

//接收变更通知
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context;

这里的change可以通过刚才上面说的key去获取一些数据,当然你也可以通过obj对象(这个就是你注册的时候的对象)的keypath去获取数据


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多