zoukankan      html  css  js  c++  java
  • KVO/KVC总结

     

    KVO/KVC总结

     
     
     

    下面是根据网上文章的总结,方便查看。

    在网上看别人的文章,了解KVC、KVO,有个kvo-kvc的例子,就是改变数组的内容(插入和删除),同步改变tableview中的内容。运行了代码之后,想添加修改数组时改变tableview内容,但是一直不能调用观察函数,后来又查了点资料,原来,数组的kvc是都是有固定格式的函数名字。把改后的工程放到资源里面了。供大家下载。下面是拷贝过来的资料。

    一.KVC和KVO的概念

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

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

    二.KVC介绍

    1、概述

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

    当使用KVO、Core Data、CocoaBindings、AppleScript(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的实现细节

      搜索Setter、Getter方法

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

       搜索简单的成员

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

       a. setValue:forKey的搜索方式:

         首先搜索set<Key>:方法

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

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

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

         那么按_<key>,_is<Key>,<key>,is<key>的顺序搜索成员名。

         如果找到设置成员的值,如果没有调用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:。

    查找有序集合成员,比如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:。

    搜索无序集合成员,如: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还提供了下面的功能

    值的正确性核查

    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。

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

    集合操作

    集合操作通过对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得知发生了哪种变化,从而进行相应的处理。

     



     
  • 相关阅读:
    面向对象1 继承与接口
    简易版爬虫(豆瓣)
    调用模块与包
    正则表达式2 以及configparser模块,subprocess模块简单介绍
    正则表达式(re模块)
    sys,logging,json模块
    常用模块(time,os,random,hashlib)
    内置函数与匿名函数
    day 19 yeild的表达式 面向过程 内置函数
    mysql中写存储过程加定时任务
  • 原文地址:https://www.cnblogs.com/iOS-mt/p/4115148.html
Copyright © 2011-2022 走看看