iOS编程——Objective-C KVO/KVC机制[转]

简介:

这两天在看和这个相关的的内容,全部推翻重写一个版本,这是公司内做技术分享的文档总结,对结构、条理做了更清晰的调整。先找了段代码,理解下,网上看到最多的一段的关于KVC的代码

先上代码 

1.     1 .Person 

2.     @implementation Person 

3.     @synthesize name,age;//属性name 将被监视 

4.     -(void) changeName 

5.     

6.         name=@"changeName directly"; 

7.     

8.     @end 

9.      

10.   

11.  2.PersonMonitor  监视了name属性 

12.  @implementation PersonMonitor 

13.   

14.  - (void)observeValueForKeyPath:(NSString *)keyPath 

15.                        ofObject:(id)object 

16.                          change:(NSDictionary *)change 

17.                         context:(void *)context 

18.  

19.      if ([keyPath isEqual:@"name"]) 

20.      { 

21.          NSLog(@"change happen, old:%@   new:%@",[change objectForKey:NSKeyValueChangeOldKey],[change objectForKey:NSKeyValueChangeNewKey]); 

22.      } 

23.  

24.  @end 

25.   

26.   

27.  3测试代码 

28.   

29.     //初始化被监视对象 

30.      Person *p =[[Person alloc] init]; 

31.     //监视对象 

32.     PersonMonitor *pm= [[PersonMonitor alloc]init]; 

33.      [p addObserver:pm forKeyPath:@"name" options:(NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld) context:nil]; 

34.     

35.  //测试前的数据 

36.      NSLog(@"p.name is %@",p.name); 

37.      

38.  //通过setvalue 的方法,PersonMonitor的监视将被调用 

39.    [p setValue:@"name kvc" forKey:@"name"]; 

40.    

41.  //查看设置后的值 

42.     NSLog(@"p name get by kvc is %@",[p valueForKey:@"name"]); 

43.   

44.  //效果和通过setValue 是一致的     

45.  p.name=@"name change by .name="; 

46.   

47.   //通过person自己的函数来更改name  

48.       [p changeName];  

49.   

50.   结果是 

51.  输出 

52.  2011-07-03 16:35:57.406 Cocoa[13970:903] p.name is name 

53.  2011-07-03 16:35:57.418 Cocoa[13970:903] change happen, old:name   new:name kvc 

54.  2011-07-03 16:35:57.420 Cocoa[13970:903] p name get by kvc is name kvc 

55.  2011-07-03 16:35:57.421 Cocoa[13970:903] change happen, old:name kvc   new:name change by .name= 

56.  最后一次修改是直接修改  所以没法产生通知 

基本概念

MODEL

主要是英文文档里面经常出现的一些概念,讲解一下,方便英文文档的阅读。

IOS应用开发是遵循MVC设计模式的,Cocoa框架用Object Modeling的规则来规范一个Model的实现。

ObjectModeling有如下几个概念的规定:

Entity:表示持有数据的一个实体

Property实体中的成员,分为Attribute和:Relationship

Attribute:基本类型的成员,比如:数字、NSString

Relationship:指向其它Entity的关系型成员,它又有to 1Relationshipto manyRelationship的区别。

AccessorMethodgettersetter

举例:

如下是一个部门和员工关系的Model

部门:Department

 

部门名称(NSString)

成员(NSArray)

部长(Employee)

MIC

(所有成员)

老王(一个成员)

MIB

 

 

员工:Employee

 

名字(NSStirng)

所属部门(Department)

小王

MIC

 

 

 

使用KVCKVO的优势

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

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

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

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

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<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方法,找到直接调用。如果是boolint等内建值类型,会做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>:atIndexesremove<Key>AtIndexes:格式的方法。

如果至少一个insert方法和至少一个remove方法找到,那么同样返回一个可以响应NSMutableArray所有方法的代理集合。那么发送给这个代理集合的NSMutableArray消息方法,以insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:insert<Key>:atIndexesremove<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. 如果recieverManagedObejct,那么就不会继续搜索了。

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

小结

KvoCocoa的一个重要机制,他提供了观察某一属性变化的方法,极大的简化了代码。这种观察-被观察模型适用于这样的情况,比方说根据A(数 据类)的某个属性值变化,Bview类)中的某个属性做出相应变化。对于推崇MVCcocoa而言,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 conventionsettergetter命名一致)。对于未定义的属性可以用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得知发生了哪种变化,从而进行相应的处理。

KVC 就是一种通过字符串去间接操作对象属性的机制, 

访问一个对象属性我们可以 person.age  也可以通过KVC的方式   [person valueForKey:@"age"]

keypath 就是属性链式访问  如 person.address.street  有点象java里面的pojo  ognl表达式子类的

假如给出的字符串没有对象的属性 会访问valueForUndefineKey方法 默认实现是raise 一个异常但你可以重写这个方法, setValue的时候也是一样的道理

key path accounts.transactions.payee would return an array with all the payee objects, for all the transactions, in all the accounts.

当设置一个非对象属性为nil时会抛异常, 但你也可以重写方法

KVC就是一个在语言框架层面实现的观察者模式 通过KVC的方式修改属性时,会主动通知观察者

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处!





















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/3272866.html ,如需转载请自行联系原作者

相关文章
|
5天前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
10天前
|
安全 数据安全/隐私保护 Android开发
深入探索iOS系统安全机制:从基础到高级
本文旨在全面解析iOS操作系统的安全特性,从基础的权限管理到高级的加密技术,揭示苹果如何构建一个既开放又安全的移动平台。我们将通过实例和分析,探讨iOS系统如何保护用户数据免受恶意软件、网络攻击的威胁,并对比Android系统在安全性方面的差异。
|
29天前
|
存储 安全 数据安全/隐私保护
探索安卓与iOS的隐私保护机制####
【10月更文挑战第15天】 本文深入剖析了安卓和iOS两大操作系统在隐私保护方面的策略与技术实现,旨在揭示两者如何通过不同的技术手段来保障用户数据的安全与隐私。文章将逐一探讨各自的隐私控制功能、加密措施以及用户权限管理,为读者提供一个全面而深入的理解。 ####
51 1
|
2月前
|
Swift iOS开发 UED
揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【9月更文挑战第5天】本文通过具体案例介绍如何在iOS应用中使用Swift与UIKit实现自定义按钮动画,当用户点击按钮时,按钮将从圆形变为椭圆形并从蓝色渐变到绿色,释放后恢复原状。文中详细展示了代码实现过程及动画平滑过渡的技巧,帮助读者提升应用的视觉体验与特色。
61 11
|
2月前
|
设计模式 前端开发 Swift
探索iOS开发:Swift与Objective-C的较量
在这篇文章中,我们将深入探讨iOS开发的两大编程语言——Swift与Objective-C。我们将分析这两种语言的特性、优势和局限性,并讨论它们在现代iOS开发中的应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和建议。
57 3
|
3月前
|
Swift iOS开发 UED
【绝妙创意】颠覆你的视觉体验!揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【8月更文挑战第13天】本文通过一个具体案例,介绍如何使用Swift与UIKit在iOS应用中创建独特的按钮动画效果。当按钮被按下时,其形状从圆形变化为椭圆形,颜色则从蓝色渐变为绿色;释放后,动画反向恢复原状。利用UIView动画方法及弹簧动画效果,实现了平滑自然的过渡。通过调整参数,开发者可以进一步优化动画体验,增强应用的互动性和视觉吸引力。
51 7
|
3月前
|
安全 测试技术 调度
iOS开发-多线程编程
【8月更文挑战第12天】在iOS开发中,属性的内存管理至关重要,直接影响应用性能与稳定性。主要策略包括:`strong`(强引用),保持对象不被释放;`weak`(弱引用),不保持对象,有助于避免循环引用;`assign`(赋值),适用于基本数据类型及非指针对象类型;`copy`(复制),复制对象而非引用,确保不变性。内存管理基于引用计数,利用自动引用计数(ARC)自动管理对象生命周期。此外,需注意避免循环引用,特别是在block中。最佳实践包括理解各策略、避免不必要的强引用、及时释放不再使用的对象、注意block中的内存管理,并使用工具进行内存分析。正确管理内存能显著提升应用质量。
|
3月前
|
开发工具 iOS开发 容器
【Azure Blob】关闭Blob 匿名访问,iOS Objective-C SDK连接Storage Account报错
【Azure Blob】关闭Blob 匿名访问,iOS Objective-C SDK连接Storage Account报错
|
4月前
|
调度 Swift Android开发
苹果iOS新手开发之Swift中的并发任务和消息机制
Swift的消息机制类似Android的Handler,实现任务调度有三种方式: 1. **Grand Central Dispatch (GCD)**:使用`DispatchQueue`在主线程或后台线程执行任务。 2. **OperationQueue**:提供高级接口管理`Operation`对象。 3. **RunLoop**:处理事件如输入源、计时器,类似Android的`Looper`和`Handler`。 **示例**: - GCD:在不同线程执行代码块。 - OperationQueue:创建操作并执行。 - RunLoop:用Timer添加到RunLoop中。
99 2
|
4月前
|
移动开发 开发工具 Android开发
探索安卓与iOS开发的差异:平台特性与编程实践
【7月更文挑战第8天】在移动开发的广阔天地中,安卓和iOS这两大操作系统各自占据着半壁江山。它们在用户界面设计、系统架构及开发工具上展现出截然不同的特色。本文将深入探讨这两个平台在技术实现和开发生态上的关键差异,并分享一些实用的开发技巧,旨在为跨平台开发者提供有价值的见解和建议。