KVC\KVO介绍

转载自:http://blog.csdn.net/zhaozy55555/article/details/8598374

仅供自我学习,感谢楼主。



一.KVC和KVO的概念

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

2> KVO:NSKeyValueObserving的简称,当指定的对象的属性被修改了,允许对象接收到通知的机制。

二.KVC介绍

1、概述

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

当使用KVOCore DataCocoaBindingsAppleScript(Mac支持)时,KVC是关键技术。

2、如何使用KVC

关键方法定义在:NSKeyValueCodingprotocol

KVC支持类对象和内建基本数据类型。

  获取值

valueForKey:,传入NSString属性的名字。

valueForKeyPath:,传入NSString属性的路径,xx.xx形式。

valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。

  修改值

setValue:forKey:

setValue:forKeyPath:

setValue:forUndefinedKey:

setNilValueForKey: 当对非类对象属性设置nil时,调用,默认抛出异常。

       一对多关系成员的情况

mutableArrayValueForKey:有序一对多关系成员  NSArray

mutableSetValueForKey:无序一对多关系成员  NSSet

3、KVC的实现细节

  搜索SetterGetter方法

 这一部分比较重要,能让你了解到KVC调用之后,到底是怎样获取和设置类成员值的。

   搜索简单的成员

     如:基本类型成员,单个对象类型成员:NSIntegerNSString*成员。

   a. setValue:forKey的搜索方式:

     首先搜索set:方法

      如果成员用@property@synthsize处理,因为@synthsize告诉编译器自动生成set:格式的setter方法,所以这种情况下会直接搜索到。

      注意:这里的是指成员名,而且首字母大写。下同。

     上面的setter方法没有找到,如果类方法accessInstanceVariablesDirectly返回YES(注:这是NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)

     那么按__isis的顺序搜索成员名。

     如果找到设置成员的值,如果没有调用setValue:forUndefinedKey:

   b. valueForKey:的搜索方式:

1. 首先按getis的顺序查找getter方法,找到直接调用。如果是boolint等内建值类型,会做NSNumber的转换。

2. 上面的getter没有找到,查找countOfobjectInAtIndex:AtIndexes格式的方法。

如果countOf和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)NSArray消息方法,就会以countOfobjectInAtIndex:AtIndexes这几个方法组合的形式调用。还有一个可选的get:range:方法。

3. 还没查到,那么查找countOfenumeratorOfmemberOf:格式的方法。

如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)NSSet消息方法,就会以countOfenumeratorOfmemberOf:组合的形式调用。

4. 还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按__isis的顺序直接搜索成员名。

5. 再没查到,调用valueForUndefinedKey:

查找有序集合成员,比如NSMutableArray

mutableArrayValueForKey:搜索方式如下:

1. 搜索insertObject:inAtIndex:removeObjectFromAtIndex:或者insert:atIndexesremoveAtIndexes:格式的方法。

如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:inAtIndex:removeObjectFromAtIndex:insert:atIndexesremoveAtIndexes:组合的形式调用。还有两个可选实现的接口:replaceObjectInAtIndex:withObject:replaceAtIndexes:with:

2. 否则,搜索set:格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set:方法。

也就是说,mutableArrayValueForKey取出的代理集合修改后,用set:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。

3. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_的顺序直接搜索成员名。如果找到,那么发送的NSMutableArray消息方法直接转交给这个成员处理。

4. 再找不到,调用setValue:forUndefinedKey:

搜索无序集合成员,如:NSSet

mutableSetValueForKey:搜索方式如下:

1. 搜索addObject:removeObject:或者add:remove:格式的方法,如果至少一个insert方法和至少一个remove方法找到,那么返回一个可以响应NSMutableSet所有方法的代理集合。那么发送给这个代理集合的NSMutableSet消息方法,以addObject:removeObject:add:remove:组合的形式调用。还有两个可选实现的接口:intersectset:

2. 如果recieverManagedObejct,那么就不会继续搜索了。

3. 否则,搜索set:格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set:方法。也就是说,mutableSetValueForKey取出的代理集合修改后,用set:重新赋值回去。这样做效率会差很多,所以推荐实现上面的方法。

4. 否则,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_的顺序直接搜索成员名。如果找到,那么发送的NSMutableSet消息方法直接转交给这个成员处理。

5. 再找不到,调用setValue:forUndefinedKey:

KVC还提供了下面的功能

值的正确性核查

KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。

实现核查方法

为如下格式:validate: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:error:格式的核查方法,找到则调用,未找到默认返回YES

注意其中的内存管理问题。

集合操作

集合操作通过对valueForKeyPath:传递参数来使用,一定要用在集合(如:array)上,否则产生运行时刻错误。其格式如下:

Left keypath部分:需要操作对象路径。

Collectionoperator部分:通过@符号确定使用的集合操作。

Rightkey path部分:需要进行集合操作的属性。

1、数据操作

@avg:平均值

@count:总数

@max:最大

@min:最小

@sum:总数

确保操作的属性为数字类型,否则运行时刻错误。

2、对象操作

针对数组的情况

@distinctUnionOfObjects:返回指定属性去重后的值的数组

@unionOfObjects:返回指定属性的值的数组,不去重

属性的值不能为空,否则产生异常。

3、数组操作

针对数组的数组情况

@distinctUnionOfArrays:返回指定属性去重后的值的数组

@unionOfArrays:返回指定属性的值的数组,不去重

@distinctUnionOfSets:同上,只是返回值为NSSet

三.KVC实现分析

KVC运用了一个isa-swizzling技术。isa-swizzling就是类型混合指针机制。KVC主要通过isa- swizzling,来实现其内部查找定位的。isa指针,如其名称所指,(就是is a kind of的意思),指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。

    比如说如下的一行KVC的代码:

[site setValue:@"sitename" forKey:@"name"];


就会被编译器处理成:

SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (site->isa,sel);
method(site, sel, @"sitename", @"name");


    首先介绍两个基本概念:

    (1)SEL数据类型:它是编译器运行Objective-C里的方法的环境参数。

    (2)IMP数据类型:他其实就是一个 编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型(事实 上,在Objective-C的编译器处理的时候,基本上都是C语言的)。

    关于如何找到实现函数的指针,可参考文章:《Objective-C如何避免动态绑定,而获得方法地址》:http://www.cocoadev.cn/Objective-C/Get-method-address.asp

    这下KVC内部的实现就很清楚的清楚了:一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环境参数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。


四.KVO介绍


Kvo是Cocoa的一个重要机制,他提供了观察某一属性变化的方法,极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数 据类)的某个属性值变化,B(view类)中的某个属性做出相应变化。对于推崇MVC的cocoa而言,kvo应用的地方非常广泛。(这样的机制听起来类 似Notification,但是notification是需要一个发送notification的对象,一般是 notificationCenter,来通知观察者。而kvo是直接通知到观察对象。)

适用kvo时,通常遵循如下流程:

1 注册:

- ( void )addObserver:(NSObject  * )anObserver forKeyPath:(NSString  * )keyPath options:(NSKeyValueObservingOptions)options context:( void * )context

keyPath就是要观察的属性值,options给你观察键值变化的选择,而context方便传输你需要的数据(注意这是一个void型)

2 实现变化方法:

- ( void ) observeValueForKeyPath:(NSString  * )keyPath ofObject:(id) object
change:(NSDictionary 
* )change context:( void * )context

change里存储了一些变化的数据,比如变化前的数据,变化后的数据;如果注册时context不为空,这里context就能接收到。

是不是很简单?kvo的逻辑非常清晰,实现步骤简单。

说了这么多,大家都要跃跃欲试了吧。可是,在此之前,我们还需要了解KVC机制。其实,知道了kvo的逻辑只是帮助你理解而已,要真正掌握的,不在 于kvo的实现步骤是什么,而在于KVC,因为只有符合KVC标准的对象才能使用kvo(强烈推荐要使用kvo的人先理解KVC)。

KVC是一种间接访问对象属性(用字符串表征)的机制,而不是直接调用对象的accessor方法或是直接访问成员对象。

key就是确定对象某个值的字符串,它通常和accessor方法或是变量同名,并且必须以小写字母开头。Key path就是以“.”分隔的key,因为属性值也能包含属性。比如我们可以person这样的key,也可以有key.gender这样的key path。

获取属性值时可以通过valueForKey:的方法,设置属性值用setValue:forKey:。与此同时,KVC还对未定义的属性值定义了 valueForUndefinedKey:,你可以重载以获取你要的实现(补充下,KVC定义载NSKeyValueCoding的非正式协议里)。

在O-C 2.0引入了property,我们也可以通过.运算符来访问属性。下面直接看个例子:

@property NSInteger number;

instance.number 
= 3 ;
[instance setValue:[NSNumber numberWithInteger:
3 ] forKey: @" number " ];

注意KVC中的value都必须是对象。

以上介绍了通过KVC来获取/设置属性,接下来要说明下实现KVC的访问器方法(accessor method)。Apple给出的惯例通常是:

-key:,以及setKey:(使用的name convention和setter/getter命名一致)。对于未定义的属性可以用setNilValueForKey:。

至此,KVC的基本概念你应该已经掌握了。之所以是基本,因为只涉及到了单值情况,kvc还可以运用到对多关系,这里就不说了,留给各位自我学习的空间

接下来,我们要以集合为例,来对掌握的KVC进行一下实践。

之所以选择array,因为在ios中,array往往做为tableview的数据源,有这样的一种情况:

 假设我们已经有N条数据,在进行了某个操作后,有在原先的数据后多了2条记录;或者对N中的某些数据进行更新替换。不使用KVC我们可以使用 reloadData方法或reloadRowsAtIndexPaths。前一种的弊端在于如果N很大消耗就很大。试想你只添加了几条数据却要重载之前 N数据。后一种方法的不足在于代码会很冗余,你要一次计算各个indexPath再去reload,而且还要提前想好究竟在哪些情况下会引起数据更新,

倘若使用了KVC/kvo,这样的麻烦就迎刃而解了,你将不用关心追加或是更新多少条数据。

下面将以添加数据为例,说明需要实现的方法:

实现insertObject:inKeyAtIndex:或者insertKey:atIndexes。同时在kvo中我们可以通过change这个dictionary得知发生了哪种变化,从而进行相应的处理。

你可能感兴趣的:(IOS)