• 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得知发生了哪种变化,从而进行相应的处理。

     



     
  • 相关阅读:
    CSS3 target伪类简介
    不用position,让div垂直居中
    css3 在线编辑工具 连兼容都写好了
    a标签伪类的顺序
    oncopy和onpaste
    【leetcode】1523. Count Odd Numbers in an Interval Range
    【leetcode】1518. Water Bottles
    【leetcode】1514. Path with Maximum Probability
    【leetcode】1513. Number of Substrings With Only 1s
    【leetcode】1512. Number of Good Pairs
  • 原文地址:https://www.cnblogs.com/iOS-mt/p/4115148.html
Copyright © 2020-2023  润新知