kvc co福特trollerr怎么获得model的数据

iOS开发之MVC设计模式 KVO模式 KVC模式 单例模式
下载简书移动应用
写了19475字,被31人关注,获得了39个喜欢
iOS开发之MVC设计模式 KVO模式 KVC模式 单例模式
MVC设计模式
模型-视图-控制器(Model-View-Controller,MVC),是Xerox PARC在20世纪80年代为编程语言Smalltalk-80发明的一种软件设计模式,至今已广泛应用于用户交互应用程序中。在iOS开发中MVC的机制被使用的淋漓尽致,充分理解iOS的MVC模式,有助于我们程序的组织合理性。
model_view_controller
1.模型对象(Model层)
模型对象封装了应用程序的数据,并定义操控和处理该数据的逻辑和运算。例如,模型对象可能是表示游戏中的角色或地址簿中的联系人。用户在视图层中所进行的创建或修改数据的操作,通过控制器对象传达出去,最终会创建或更新模型对象。模型对象更改时(例如通过网络连接接收到新数据),它通知控制器对象,控制器对象更新相应的视图对象。简单来理解就是后台数据库与app之间的交流,前端变化后台会跟着变化 实现数据库的增删改查(1)创建数据库、创建相应的表(2)完成针对数据库各个表的增、删、改、查的操作类(3)映射数据库各个表的实体类(这个实体类的作用就是沟通数据库层(M)和控制层(C)的桥梁,同时这个实体类也将担负其后台数据(xml、sbjson等)与本地数据的沟通和存储)  本层要实现的功能:  (1)  本层输入件:sql增加或插入数据库表对应的实体类的对象的语句  本层输出件:增加、或插入数据库  (2)  本层输入件:sql查询语句  本层输出件:返回存储实体类对象的数组  (3)  本层输入件:sql删除语句  本层输出件:删除数据库中的指定信息
2.视图对象(View层)(storyboard和xib或者在.m中写)
视图对象是应用程序中用户可以看见的对象。视图对象知道如何将自己绘制出来,并可能对用户的操作作出响应。视图对象的主要目的,就是显示来自应用程序模型对象的数据,并使该数据可被编辑。尽管如此,在 MVC 应用程序中,视图对象通常与模型对象分离。
在iOS应用程序开发中,所有的控件、窗口等都继承自 UIView,对应MVC中的V。UIView及其子类主要负责UI的实现,而UIView所产生的事件都可以采用委托的方式,交给UIViewController实现。
本层实现的功能就是控件的布局。
3.控制器对象(controller 就是.m文件)
在应用程序的一个或多个视图对象和一个或多个模型对象之间,控制器对象充当媒介。控制器对象因此是同步管道程序,通过它,视图对象了解模型对象的更改,反之亦然。控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。控制器对象解释在视图对象中进行的用户操作,并将新的或更改过的数据传达给模型对象。模型对象更改时,一个控制器对象会将新的模型数据传达给视图对象,以便视图对象可以显示它。主要作用  本层输入件:界面控件中数据和事件  本层输出件:  第一:调用M层的接口,更新M层(数据库)中的数据  第二:调用V层的接口,更新V层(界面)中的数据
对于不同的UIView,有相应的UIViewController,对应MVC中的C。例如在iOS上常用的UITableView,它所对应的Controller就是UITableViewController。
1.Model和View永远不能相互通信,只能通过Controller传递。2.Controller可以直接与Model对话(读写调用Model),Model通过Notification和KVO机制与Controller间接通信。3.Controller可以直接与View对话,通过outlet,直接操作View,outlet直接对应到View中的控件,View通过action向Controller报告事件的发生(如用户Touch我了)。Controller是View的直接数据源(数据很可能是Controller从Model中取得并经过加工了)。Controller是View的代理(delegate),以同步View与Controller。
利用MVC思想组织的文件结构一例:
**当然,软件中还有如下的几种代码**  现实中,工程中还有以下几种类型的代码:  (1)接口文件[数据操作]  (2)解析通过接口获取的数据[数据操作]  (3)开源框架(实现各种界面效果、解析各种数据)[数据操作+V显示]  (4)工具类(比如为图片增加圆角、实现checkbox、实现各种页面效果、数据加密解密)[数据操作+V显示]  (5)本项目提炼的公用类(如验证、升级检测、数据更新等)[数据操作M]所以还是需要自己去总结精炼自己的项目的(文件千万别瞎放,后来看可恶心了。。。说多了都是泪。。。)
有关”模型-视图-控制器”的完整信息,请参阅 Concepts in Objective-C Programming(Objective-C 编程中的概念)中的:
KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
KVO其实也是“观察者”设计模式的一种应用。我的看法是,这种模式有利于两个类间的解耦合,尤其是对于 业务逻辑与视图控制 这两个功能的解耦合。
KVO这一机制是基于NSKeyValueObserving协议的,Cocoa通过这个协议为所有遵循协议的对象提供了自动观察属性变化的能力。在NSObject中已经为我们实现了这一协议,所以我们不必去实现这个协议。
为什么要使用KVO?
有的朋友可能会有疑问,为什么要使用KVO呢?KVO能实现的我使用Setter方法同样能实现啊。其实不然KVO存在还是有它的价值的,那么接下来我们细数一下KVO的独特价值吧:
1.我们创建一两个setter方法感觉没什么,但是如果要观察的属性非常多,那么还能一一重写setter方法来实现吗?想必大家心里已有了答案,但是利用KVO则能很好的解决上述问题。
2.我们自定义的类是很容易改写setter方法的,但是如果你是用一个已经编译好了的类库时要监控其中一个属性时怎么办?难道还要去重写setter方法?如果使用KVO则很轻松解决问题。
3.使用KVO能够方便的记录变化前的值和变化后的值,不适用KVO你还要自己来解决这些问题。
4.KVO让你的代码看起来更加简洁清晰易于维护。
如何使用KVO呢?
首先,你要为你想观察的对象添加一个观察者代码如下:
[object addObserver: observer forKeyPath: @"frame" options: 0 context: nil];
调用方法是:object : 被观察对象observer: 观察对象forKeyPath里面带上property的name,如UIView的frame、center等等options: 有4个值,分别是:NSKeyValueObservingOptionNew 把更改之前的值提供给处理方法NSKeyValueObservingOptionOld 把更改之后的值提供给处理方法NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。注:例子里的0就代表不带任何参数进去context: 可以带入一些参数,任何类型都可以,强制转就可以。接下来观察到值的变化后该应该调用相关的方法来处理,这个方法是:
- (void)observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary )change context:(void *)context
其中:keyPath: 对应forKeyPathobject:
被观察的对象change:
对应options里的NSKeyValueObservingOptionNew、NSKeyValueObservingOptionOld等context: 对应context
Screen Shot
at 4.23.33 PM
通过滑动slider来改变相关的值,相关的只发生变化后从而改变标签的颜色。下载地址:代码来自
KVC的基本概念
Key-value coding,它是一种使用字符串标识符,间接访问对象属性的机制,而不是直接调用getter 和 setter方法。通常我们使用valueForKey 来替代getter 方法,setValue:forKey来代替setter方法。
下面是使用KVC 和 不使用 KVC的代码对比
Persion *persion = [ [Persion alloc] init ];//不使用KVCpersion.name = @"hufeng" ;//使用KVC的写法[persion setValue:@"hufeng" forKey:@"name"];
看出区别来了吗?你可能会说 你写的太简单了,我们实际用的时候不可能有这样复杂的类,下面我们写个复杂点的:我们有一个人 这个人有一个手机类 这个手机类 有一个电池类 我们要获取这个电池类 比之前复杂了吧。没有KVC
Persion persion = [ [Persion alloc] init ];Phone phone = persion.Battery *battery = phone.
Battery *battery = [persion valueForKeyPath: @"phone.battery" ];
注意- valueForKeyPath 里面的值是区分大小写的,你如果写出Phone.Battery 是不行的说到这里你可能会问 我能不能对 NSArray 调用KVC吗? 答案是否定的,因为array 没有keys啊,但是你可以对array里面的item 使用KVC。KVC 最常用的还是在序列化和反序列话对象。我们经常需要把json字符串反序列化成我们想要的对象 下面是一个例子 将字典用NSKeyedArchiver 序列化成对象
-(id)initWithDictionary:(NSDictionary *)dictionary {self = [self init];if (self){[self setValuesForKeysWithDictionary:dictionary];}return self}
主要是让大家多了解一点
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
顾名思义,单例,即是在整个项目中,这个类的对象只能被初始化一次。它的这种特性,可以广泛应用于某些需要全局共享的资源中,比如管理类,引擎类,也可以通过单例来实现传值。UIApplication、NSUserDefaults等都是IOS中的系统单例。
“单例模式”是我在iOS中最常使用的设计模式之一。单例模式不需要传递任何参数,就有效地解决了不同代码间的数据共享问题。
iOS中的单例模式应用iOS中好几个类都是采用了单例模式,比如NSApplication, NSFontManager,
NSDocumentController,NSHelpManager, NSNull,NSProcessInfo, NSScriptExecutionContext,
NSUserDefaults.
关于单例的写法,因为我上网看了好多种都和我看的不太一样写的,所以我就不贴了,想看的可以去我的另一篇文章里有
写的不好请见谅,好多文章归拢的东西不太会用。。。还需要学习
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
玩转简书的第一步,从这个专题开始。
想上首页热门榜么?好内容想被更多人看到么?来投稿吧!如果被拒也不要灰心哦~入选文章会进一个队...
· 114015人关注
分享 iOS 开发的知识,解决大家遇到的问题,讨论iOS开发的前沿,欢迎大家投稿~
· 22362人关注
Write the Code, Change the World, 改变世界从编程开始, 收集编程相关的干货
· 10858人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:iOS KVC KVO KVB - Hibernate4 - 博客园
&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语言的)。
这下KVC内部的实现就很清楚的清楚了:一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环境参 数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。
Key-Value Observing机制的概述
Key-Value Observing (简写为KVO):当指定的对象的属性被修改了,允许对象接受到通知的机制。每次指定的被观察对象的属性被修改的时候,KVO都会自动的去通知相应的观察 者。
当有属性改变,KVO会提供自动的消息通知。这样的架构有很多好处。首先,开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。这 是KVO机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。开发人员不需要添加任何代码,不需要设计自己的观察者模 型,直接可以在工程里使用。其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同一个属性,以及相关的值。
两个基本方法
1:为对象添加观察者OBserver
2:观察者OBserver收到信息的处理函数
MVC架构是&Model-View-Controller&的缩写,中文翻译为&模式-视图-控制器&。MVC应用程序总是由这三个部分组成。 Event(事件)导致Controller改变Model或View,或者同时改变两者。只要Controller改变了Models的数据或者属性, 所有依赖的View都会自动更新。类似的,只要Controller改变了View,View会从潜在的Model中获取数据来刷新自己。
  MVC架构最早是smalltalk语言研究团提出的,应用于用户交互应用程序中。
  MVC模式是一个复杂的架构模式,其实现也显得非常复杂。但是,我们已经总结出了很多可靠的设计模式,多种设计模式结合在一起,使MVC模式的 实现变得相对简单易行。Views可以看作一棵树,显然可以用Composite Pattern来实现。Views和Models之间的关系可以用Observer Pattern体现。Controller控制Views的显示,可以用Strategy Pattern实现。Model通常是一个调停者,可采用Mediator
Pattern来实现。
KVC--KVO--KVB优势
些机制通过规定了一组通用的Cocoa命名法则、调用规则等,实现了如下功能:
&&&1.&使用一对高度规范化的访问方法,获取以及设置任何对象的任何属性的值(所谓的属性既可以是个实实在在的成员变量,也可以是通过一对成员方法所抽象出的该对 象的一个性质)。
&& 2.&通过继承一个特定的方法,并且指定希望监视的对象及希望监视的属性名称,就能在该对象的指定属性的值发生改变时,得到一个“通知”(尽管这不是一个真正意 义上的通知),并且得到相关属性的值的变化(原先的值和改变后的新值)。
&& 3.&通过一个简单的函数调用,使一个视图对象的一个指定属性随时随地都和一个控制器对象或模型对象的一个指定属性保持同步。
在很多时候接触到很多地方都有对KVC,KVO的描述,但是都是一笔带过.只知道这是Object-C提供的一个不错的机制,可以很好的减少浇水代码。
其实KVC、KVO即NSKeyValueCoding和NSKeyValueCoding的简称。
在官方文档中描述为
那我们KVO、KVC用来做什么的我们又怎么使用它呢?
首先我们先了解下KVO的机制
KVO:当指定的对象的属性被修改了,允许对象接收到通知的机制。每当在类中定义一个监听
[self&addObserver:self
&&forKeyPath:@&items&
&&options:0
&&context:contexStr];
当然你还可以监听其他对象的属性变化
[person addObserver:money
&&forKeyPath:@&account&
&&options:0
&&context:contexStr];
只要当前类中items这个属性发生的变化都会触发到以下的方法。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void&*)context
KVO的优点:
当有属性改变,KVO会提供自动的消息通知。这样开发人员不需要自己去实现这样的方案:每次属性改变了就发送消息通知。
这是KVO机制提供的最大的优点。因为这个方案已经被明确定义,获得框架级支持,可以方便地采用。
开发人员不需要添加任何代码,不需要设计自己的观察者模型,直接可以在工程里使用。
其次,KVO的架构非常的强大,可以很容易的支持多个观察者观察同 一个属性,以及相关的值。
KVC的实现分析
KVC运用了一个isa-swizzling技术。
isa-swizzling就是类型混合指针机制。KVC主要通过isa-swizzling,来实现其内部查找定位的。
isa指针,就是is
a kind of的意思,指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。
如下KVC的代码:
[person setValue:@&personName&&forKey:@&name&];
就会被编译器处理成:
SEL&sel = sel_get_uid (&setValue:forKey:&);
IMP&method = objc_msg_lookup (person-&isa,sel);
method(person, sel,&@&personName&,&@&name&);
SEL数据类型:它是编译器运行Objective-C里的方法的环境参数。
IMP数据类型:他其实就是一个 编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。
KVC在调用方法setValue的时候
(1)首先根据方法名找到运行方法的时候所需要的环境参数。
(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。
(3)再直接查找得来的具体的方法实现。
这样的话前面介绍的KVO实现就好理解了
当一个对象注册了一个观察者,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。
所以isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名。
这样只有当我们调用KVC去访问key值的时候KVO才会起作用。所以肯定确定的是,KVO是基于KVC实现的。
随笔 - 599本周问答荣誉榜
本月问答荣誉榜
含有标签"KVC"的问题
KVC 是否可通过setvalue forkey &去设置一个实例变量 即这个类没有使用@property 即没有定义存取方法
#define a 10;
NSMutableDictionary *
for(int i = 0;i&10; i++)
UIButton * btn = [[[UIButton alloc]init]autorelease];
btn.tag = i+a;
if(i == 5 && a == 10)
[model setValue:btn forKey:@"btn"];
我用可变字典存了这个btn,这个btn.tag现在为15
然后我把 #define a &15; 我把a 变为15了
这样for循环里的if判断就不执行 不执行&setValue:&forKey: 也不执行
但是&[model&valueForKey:@"btn"];
btn.tag 变为20了...
我学到的kvc是可以给对象赋值,但是前提是这个对象有这个属性,如果对象不存在这个属性,使用kvc赋值就会报错:没有这个属性
但是我在很多系统对象发现了奇怪的现象:我可以给任意key赋值,而且可以取出来,
比如:CABasicAnimation对象就是这样。你使用kvc给随意的key都可以赋值,而且可以取出来,怎么回事?????什么原因??
ios kvc &kvo &大家都是怎么理解的啊 & 大神们讲讲 & &互相学习一下
如何通过一个类型得到它的全部属性信息?
只看到这个获得全部属性名的方法:
+ (NSArray *)getPropertyNameList:(Class)class
&&& objc_property_t *properties = class_copyPropertyList(class, &count);
&&& NSMutableArray *propertyNameArray = [NSMutableArray arrayWithCapacity:count];
&&& for (int i = 0; i & i++)
&&&&&&& const char *propertyAttributes = property_getAttributes(properties[i]);
&&&&&&& const char *propertyName = property_getName(properties[i]);
&&&&&&& [propertyNameArray addObject: [NSString stringWithUTF8String: propertyAttributes]];
&&&&&&& [propertyNameArray addObject: [NS...
问题:为什么在使用kvo时,使用属性访问和使用kvc访问,对kvo造成不同影响?代码如下:
@interface Boom : NSObject
@property (nonatomic,assign) NSInteger bombT
#import \\\"Boom.h\\\"
@implementation Boom
-(id) init
if (self = [super init]) {
_bombTimer = 60;
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
-(void) timerAction:(NSTimer *) timer
self.bombTimer--;
在这里,使用self点语法访问时,和使用kvc访问有不同结果
13:53:02.573 oc-5.9[] {
[self setValue:[NSNumber numberWithInteger:_bombTimer] forKey:@\\\"bombTimer\\\"];
_bombTimer--;
/* 13:53:02.573 oc-5.9[] {
@interface BoomTimer :...
环境:OSX10.8,Xcode4.5
描述:我按照/library/ios/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/SearchImplementation.html#//apple_ref/doc/uid/-CJBBBFFA& 描述
If the countOf&Key& method and at least one of the other two possible methods are found, a collection proxy object that responds to all NSArray methods is returned. Each NSArray message sent to the collection proxy object will result in some combination of countOf&Key&, objectIn&Key&AtIndex:, and &key&AtIndexes: messages being sent to the original receiver of valueForKey:. If the class of the receiver also implements an optional method whose name matches the pattern get&Key&:range: that method will be used when appropriate for best performance.
于是我定义了一个属性 NSArray* _
@property(nonatomic,retain) NSArray *
并且实现 -countOfArrs
NSlog("here");
} 与 -objectInArrsA...
我 有一个控制器对象
NewsViewController *newsView =[ [NewsViewController alloc]init];
然后我使用了KVC 保存了一个键值对,测试通过了
然后我又设置了一个键值对
然后报错是这样的:
17:10:18.167 KeBoHui[4780:c07] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[&NewsContentViewController 0xf41dbf0& setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key content.'
*** First throw call stack:
(0x20ca89e7e 0xxx14b75f8 0x14b70e7 0xe5d4 0xa7d285 0xa7d4ed 0xxxx206fa82 0x206ef44 0x206ee1b 0x25a57e3 0x25acdffc 0xd5)
libc++abi.dylib: terminate called throwing an exception
我有两个问题:一个问题是KVC是不是一个对象只能使用一次?可不可以改变键值对?
第二个问题是,上述问题是怎么产生的?怎么解决呢?谢谢。...
各位大大今天我遇到了KVC的小问题,怎么也不能observe值,是我概念不大清楚,请大家给我指点一下吧,谢谢各位。
我有一个UIViewController和一个UIView,其中UIView(myView)是UIViewController.view的子视图。
我想在UIViewController里监听MyView里一个BOOL变量(isCentered)的改变:
我在MyView里改变BOOL变量的值:
[self willChangeValueForKey:@"isCentered"];
isCentered = NO;
[self didChangeValueForKey:@"isCentered"];
我在UIViewController初始化的时候添加一个监听:
[self addObserver:self forKeyPath:@"myView.isCentered" options:NSKeyValueObservingOptionNew context:nil];
然后实现了这个方法:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
&& &if ([keyPath isEqual:@"myView.isCentered"])
&& &&& &NSLog(@"observed...详解KVC,我来告诉你KVC的一切 - IOS - 伯乐在线
& 详解KVC,我来告诉你KVC的一切
KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实翻译一下就很简单了,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的。目前网上关于KVC的文章在非常多,有的只是简单地说了下用法,有的讲得深入但是在使用场景和最佳实践没有说明,我写下这遍文章就是给大家详解一个最完整最详细的KVC。
KVC在iOS中的定义
无论是Swift还是Objective-C,KVC的定义都是对NSObject的扩展来实现的(Objective-c中有个显式的NSKeyValueCoding类别名,而Swift没有,也不需要)所以对于所有继承了NSObject在类型,都能使用KVC(一些纯Swift类和结构体是不支持KVC的),下面是KVC最为重要的四个方法
- (nullable id)valueForKey:(NSString *)
//直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)
//通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyP
//通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyP
//通过KeyPath来设值
- (nullable id)valueForKey:(NSString *)key;&&&&&&&&&&&&&&&&&&&&&&&&&&//直接通过Key来取值- (void)setValue:(nullable id)value forKey:(NSString *)key;&&&&&&&&&&//通过Key来设值- (nullable id)valueForKeyPath:(NSString *)keyPath;&&&&&&&&&&&&&&&&&&//通过KeyPath来取值- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;&&//通过KeyPath来设值
当然NSKeyValueCoding类别中还有其他的一些方法,下面列举一些
+ (BOOL)accessInstanceVariablesD
//默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outE
//KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
- (nullable id)valueForUndefinedKey:(NSString *)
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)
//和上一个方法一样,只不过是设值。
- (void)setNilValueForKey:(NSString *)
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
1234567891011121314
+ (BOOL)accessInstanceVariablesDirectly;//默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;//KVC提供属性值确认的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回- (nullable id)valueForUndefinedKey:(NSString *)key;//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;//和上一个方法一样,只不过是设值。- (void)setNilValueForKey:(NSString *)key;//如果你在SetValue方法时面给Value传nil,则会调用这个方法- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
上面的这些方法在碰到特殊情况或者有特殊需求还是会用到的,所以也是可以了解一下。后面的代码示例会有讲到其中的一些方法。
同时苹果对一些容器类比如NSArray或者NSSet等,KVC有着特殊的实现。建议有基础的或者英文好的开发者直接去看苹果的官方文档,相信你会对KVC的理解更上一个台阶。
KVC是怎么寻找Key的
KVC是怎么使用的,我相信绝大多数的开发者都很清楚,我在这里就不再写简单的使用KVC来设值和取值的代码了,首页我们来探讨KVC在内部是按什么样的顺序来寻找key的。
当调用setValue:属性值 forKey:@”name“的代码时,底层的执行机制如下:
程序优先调用set:属性值方法,代码通过setter方法完成设置。注意,这里的是指成员变量名,首字母大清写要符合KVC的全名规则,下同
如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为_的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只在存在以_命名的变量,KVC都可以对该成员变量赋值。
如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量,
和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUNdefinedKey:方法,默认是抛出异常。
如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUNdefinedKey:方法。
下面我们来让代码来测试一下上面的KVC机制
@interface Dog : NSObject
@implementation Dog
NSString* toSetN
NSString* isN
//NSString*
NSString* _
NSString* _isN
// -(void)setName:(NSString*)name{
toSetName =
//-(NSString*)getName{
return toSetN
+(BOOL)accessInstanceVariablesDirectly{
return NO;
-(id)valueForUndefinedKey:(NSString *)key{
NSLog(@"出现异常,该key不存在%@",key);
-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"出现异常,该key不存在%@",key);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Dog* dog = [Dog new];
[dog setValue:@"newName" forKey:@"name"];
NSString* name = [dog valueForKey:@"toSetName"];
NSLog(@"%@",name);
12345678910111213141516171819202122232425262728293031323334353637
@interface Dog : NSObject@end@implementation Dog{&&&& NSString* toSetName;&&&&NSString* isName;&&&&//NSString*&&&&NSString* _name;&&&&NSString* _isName;}// -(void)setName:(NSString*)name{//&&&& toSetName =// }//-(NSString*)getName{//&&&&return toSetN//}+(BOOL)accessInstanceVariablesDirectly{&&&&return NO;}-(id)valueForUndefinedKey:(NSString *)key{&&&&NSLog(@"出现异常,该key不存在%@",key);&&&&return nil;}-(void)setValue:(id)value forUndefinedKey:(NSString *)key{&&&& NSLog(@"出现异常,该key不存在%@",key);}@endint main(int argc, const char * argv[]) {&&&&@autoreleasepool {&&&&&&&&// insert code here...&&&&&&&&Dog* dog = [Dog new];&&&&&&&&[dog setValue:@"newName" forKey:@"name"];&&&&&&&&NSString* name = [dog valueForKey:@"toSetName"];&&&&&&&&NSLog(@"%@",name);&&&&}&&&&return 0;}
首先我们先重写accessInstanceVariablesDirectly方法让其返回NO,再运行代码(注意上面注释的部分),XCode直接打印出
15:52:12.039 DemoKVC[] 出现异常,该key不存在name
15:52:12.040 DemoKVC[] 出现异常,该key不存在toSetName
15:52:12.040 DemoKVC[] (null)
2016-04-15 15:52:12.039 DemoKVC[9681:287627] 出现异常,该key不存在name2016-04-15 15:52:12.040 DemoKVC[9681:287627] 出现异常,该key不存在toSetName2016-04-15 15:52:12.040 DemoKVC[9681:287627] (null)
这说明了重写+(BOOL)accessInstanceVariablesDirectly方法让其返回NO后,KVC找不到SetName:方法后,不再去找name系列成员变量,而是直接调用forUndefinedKey方法
所以开发者如果不想让自己的类实现KVC,就可以这么做。
下面那两个setter和gettr的注释取消掉,再把
NSString* name = [dog valueForKey:@"toSetName"]; 换成 NSString* name = [dog valueForKey:@"name"];
NSString* name = [dog valueForKey:@"toSetName"]; 换成 NSString* name = [dog valueForKey:@"name"];
XCode就可以正确地打印出正确的值了
15:56:22.130 DemoKVC[] newName
2016-04-15 15:56:22.130 DemoKVC[9726:289258] newName
下面再注释到accessInstanceVariablesDirectly方法,就能测试其他的key查找顺序了,为了节省篇幅,剩下的的KVC对于key寻找机制就不在这里展示了,有兴趣的读者可以写代码去验证。
当调用ValueforKey:@”name“的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“,其搜索方式如下
首先按get,,is的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者int等值类型, 会做NSNumber转换
如果上面的getter没有找到,KVC则会查找countOf,objectInAtIndex,AtIndex格式的方法。如果countOf和另外两个方法中的要个被找到,那么就会返回一个可以响应NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子类),调用这个代理集合的方法,或者说给这个代理集合发送NSArray的方法,就会以countOf,objectInAtIndex,AtIndex这几个方法组合的形式调用。还有一个可选的get:range:方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。
如果上面的方法没有找到,那么会查找countOf,enumeratorOf,memberOf格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,以送给这个代理集合消息方法,就会以countOf,enumeratorOf,memberOf组合的形式调用。
如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_,_is,,is的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:
还没有找到的话,调用valueForUndefinedKey:
下面再上代码测试
@interface TwoTimesArray : NSObject
-(void)incrementC
-(NSUInteger)countOfN
-(id)objectInNumbersAtIndex:(NSUInteger)
@interface TwoTimesArray()
@property (nonatomic,readwrite,assign) NSUI
@property (nonatomic,copy) NSString* arrN
@implementation TwoTimesArray
-(void)incrementCount{
self.count ++;
-(NSUInteger)countOfNumbers{
return self.
-(id)objectInNumbersAtIndex:(NSUInteger)index{
//当key使用numbers时,KVC会找到这两个方法。
return @(index * 2);
-(NSInteger)getNum{
//第一个,自己一个一个注释试
return 10;
-(NSInteger)num{
return 11;
-(NSInteger)isNum{
return 12;
int main(int argc, const char * argv[]) {
@autoreleasepool {
TwoTimesArray* arr = [TwoTimesArray new];
NSNumber* num =
[arr valueForKey:@"num"];
NSLog(@"%@",num);
id ar = [arr valueForKey:@"numbers"];
NSLog(@"%@",NSStringFromClass([ar class]));
NSLog(@"0:%@
3:%@",ar[0],ar[1],ar[2],ar[3]);
[arr incrementCount];
//count加1
NSLog(@"%lu",(unsigned long)[ar count]);
[arr incrementCount];
//count再加1
NSLog(@"%lu",(unsigned long)[ar count]);
[arr setValue:@"newName" forKey:@"arrName"];
NSString* name = [arr valueForKey:@"arrName"];
NSLog(@"%@",name);
//打印结果
15:39:42.214 KVCDemo[] 10
15:39:42.215 KVCDemo[] NSKeyValueArray
15:41:24.713 KVCDemo[] 0:0
//太明显了,直接调用-(id)objectInNumbersAtIndex:(NSUInteger)方法
15:39:42.215 KVCDemo[] 1
15:39:42.215 KVCDemo[] 2
15:39:42.215 KVCDemo[] newName
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
@interface TwoTimesArray : NSObject-(void)incrementCount;-(NSUInteger)countOfNumbers;-(id)objectInNumbersAtIndex:(NSUInteger)index;@end@interface TwoTimesArray()@property (nonatomic,readwrite,assign) NSUInteger count;@property (nonatomic,copy) NSString* arrName;@end@implementation TwoTimesArray-(void)incrementCount{&&&&self.count ++;}-(NSUInteger)countOfNumbers{&&&&return self.count;&-(id)objectInNumbersAtIndex:(NSUInteger)index{&&&& //当key使用numbers时,KVC会找到这两个方法。&&&&return @(index * 2);}-(NSInteger)getNum{&&&&&&&&&&&&&&&& //第一个,自己一个一个注释试&&&&return 10;}-(NSInteger)num{&&&&&&&&&&&&&&&&&&&&&& //第二个&&&&return 11;}-(NSInteger)isNum{&&&&&&&&&&&&&&&&&&&&//第三个&&&&return 12;}@end&&int main(int argc, const char * argv[]) {&&&&@autoreleasepool {&&&&&&&&TwoTimesArray* arr = [TwoTimesArray new];&&&&&&&&NSNumber* num =&& [arr valueForKey:@"num"];&&&&&&&&NSLog(@"%@",num);&&&&&&&&id ar = [arr valueForKey:@"numbers"];&&&&&&&&NSLog(@"%@",NSStringFromClass([ar class]));&&&&&&&& NSLog(@"0:%@&&&& 1:%@&&&& 2:%@&&&& 3:%@",ar[0],ar[1],ar[2],ar[3]);&&&&&&&&[arr incrementCount];&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//count加1&&&&&&&&NSLog(@"%lu",(unsigned long)[ar count]);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //打印出1&&&&&&&&[arr incrementCount];&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//count再加1&&&&&&&&NSLog(@"%lu",(unsigned long)[ar count]);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //打印出2&&&&&&&&&[arr setValue:@"newName" forKey:@"arrName"];&&&&&&&&NSString* name = [arr valueForKey:@"arrName"];&&&&&&&&NSLog(@"%@",name);&&&&&}&&&&return 0;}//打印结果 2016-04-17 15:39:42.214 KVCDemo[1088:74481] 102016-04-17 15:39:42.215 KVCDemo[1088:74481] NSKeyValueArray2016-04-17 15:41:24.713 KVCDemo[1102:75424] 0:0&&&& 1:2&&&& 2:4&&&& 3:6&&&&&&&&&&&&&&&& //太明显了,直接调用-(id)objectInNumbersAtIndex:(NSUInteger)方法2016-04-17 15:39:42.215 KVCDemo[1088:74481] 12016-04-17 15:39:42.215 KVCDemo[1088:74481] 22016-04-17 15:39:42.215 KVCDemo[1088:74481] newName
很明显,上面的代码充分说明了说明了KVC在调用ValueforKey:@”name“时搜索key的机制。不过还有些功能没有全部列出,有兴趣的读者可以写代码去验证。
在KVC中使用KeyPath
然而在开发过程中,一个类的成员变量有可能是其他的自定义类,你可以先用KVC获取出来再该属性,然后再次用KVC来获取这个自定义类的属性,但这样是比较繁琐的,对此,KVC提供了一个解决方案,那就是键路径KeyPath。
- (nullable id)valueForKeyPath:(NSString *)keyP
//通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyP
//通过KeyPath来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath;&&&&&&&&&&&&&&&&&&//通过KeyPath来取值- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;&&//通过KeyPath来设值
@interface Address : NSObject
@interface Address()
@property (nonatomic,copy)NSString*
@implementation Address
@interface People : NSObject
@interface People()
@property (nonatomic,copy) NSString*
@property (nonatomic,strong) Address*
@property (nonatomic,assign) NSI
@implementation People
int main(int argc, const char * argv[]) {
@autoreleasepool {
People* people1 = [People new];
Address* add = [Address new];
add.country = @"China";
people1.address =
NSString* country1 = people1.address.
NSString * country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@
country2:%@",country1,country2);
[people1 setValue:@"USA" forKeyPath:@"address.country"];
country1 = people1.address.
country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@
country2:%@",country1,country2);
//打印结果
15:55:22.487 KVCDemo[] country1:China
country2:China
15:55:22.489 KVCDemo[] country1:USA
country2:USA
123456789101112131415161718192021222324252627282930313233343536
@interface Address : NSObject&@end@interface Address()@property (nonatomic,copy)NSString* country;@end@implementation Address@end@interface People : NSObject@end@interface People()@property (nonatomic,copy) NSString* name;@property (nonatomic,strong) Address* address;@property (nonatomic,assign) NSInteger age;@end@implementation People@endint main(int argc, const char * argv[]) {&&&&@autoreleasepool {&&&&&&&&People* people1 = [People new];&&&&&&&&Address* add = [Address new];&&&&&&&&add.country = @"China";&&&&&&&&people1.address = add;&&&&&&&&NSString* country1 = people1.address.country;&&&&&&&&NSString * country2 = [people1 valueForKeyPath:@"address.country"];&&&&&&&&NSLog(@"country1:%@&& country2:%@",country1,country2);&&&&&&&&[people1 setValue:@"USA" forKeyPath:@"address.country"];&&&&&&&& country1 = people1.address.country;&&&&&&&&country2 = [people1 valueForKeyPath:@"address.country"];&&&&&&&&NSLog(@"country1:%@&& country2:%@",country1,country2);&&&&}&&&&return 0;}//打印结果 2016-04-17 15:55:22.487 KVCDemo[1190:82636] country1:China&& country2:China2016-04-17 15:55:22.489 KVCDemo[1190:82636] country1:USA&& country2:USA
上面的代码简单在展示了KeyPath是怎么用的。如果你不小心错误的使用了key而非KeyPath的话,KVC会直接查找address.country这个属性,很明显,这个属性并不存在,所以会再调用UndefinedKey相关方法。而KVC对于KeyPath是搜索机制第一步就是分离key,用小数点.来分割key,然后再像普通key一样按照先前介绍的顺序搜索下去。
KVC如何处理异常
KVC中最常见的异常就是不小心使用了错误的Key,或者在设值中不小心传递了nil的值,KVC中有专门的方法来处理这些异常。
通常在用KVC操作Model时,抛出异常的那两个方法是需要重写的。虽然一般很小出现传递了错误的Key值这种情况,但是如果不小心出现了,直接抛出异常让APP崩溃显然是不合理的。
一般在这里直接让这个Key打印出来即可,或者有些特殊情况需要特殊处理。
通常情况下,KVC不允许你要在调用setValue:属性值 forKey:@”name“(或者keyPath)时对非对象传递一个nil的值。很简单,因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
[people1 setValue:nil forKey:@"age"]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[ setNilValueForKey]: could not set nil as the value for the key age.' // 调用setNilValueForKey抛出异常
&&[people1 setValue:nil forKey:@"age"]&& *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[ setNilValueForKey]: could not set nil as the value for the key age.' // 调用setNilValueForKey抛出异常
如果重写setNilValueForKey:就没问题了
@implementation People
-(void)setNilValueForKey:(NSString *)key{
NSLog(@"不能将%@设成nil",key);
16:19:55.298 KVCDemo[] 不能将age设成nil
@implementation People&-(void)setNilValueForKey:(NSString *)key{&&&&NSLog(@"不能将%@设成nil",key);}&@end//打印出2016-04-17 16:19:55.298 KVCDemo[1304:92472] 不能将age设成nil
KVC处理非对象和自定义对象
不是每一个方法都返回对象,但是valueForKey:总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开以者需要手动转换成原来的类型。尽管valueForKey:会自动将值类型封装成对象,但是setValue:forKey:却不行。你必须手动将值类型转换成NSNumber或者NSValue类型,才能传递过去。
对于自定义对象,KVC也会正确以设值和取值。因为传递进去和取出来的都是id类型,所以需要开发者自己担保类型的正确性,运行时Objective-C在发送消息的会检查类型,如果错误会直接抛出异常。
Address* add2 = [Address new];
add2.country = @"England";
[people1 setValue:add2 forKey:@"address"];
NSString* country1 = people1.address.
NSString * country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@
country2:%@",country1,country2);
//打印结果
16:29:36.349 KVCDemo[] country1:England
country2:England
Address* add2 = [Address new];add2.country = @"England";[people1 setValue:add2 forKey:@"address"];NSString* country1 = people1.address.country;NSString * country2 = [people1 valueForKeyPath:@"address.country"];NSLog(@"country1:%@&& country2:%@",country1,country2);//打印结果2016-04-17 16:29:36.349 KVCDemo[1346:95910] country1:England&& country2:England
KVC与容器类
对象的属性可以是一对一的,也可以是一对多的。一对多的属性要么是有序的(数组),要么是无序的(数组)
不可变的有序容器属性(NSArray)和无序容器属性(NSSet)一般可以使用valueForKey:来获取。比如有一个叫items的NSArray属性,你可以用valurForKey:@"items"来获取这个属性。前面valueForKey:的key搜索模式中,我们发现其实KVC使用了一种更灵活的方式来管理容器类。苹果的官方文档也推荐我们实现这些这些特殊的访问器。
而当对象的属性是可变的容器时,对于有序的容器,可以用下面的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
该方法返回一个可变有序数组,如果调用该方法,KVC的搜索顺序如下
搜索insertObject:inAtIndex: , removeObjectFromAtIndex: 或者 insertAdIndexes , removeAtIndexes 格式的方法
如果至少找到一个insert方法和一个remove方法,那么同样返回一个可以响应NSMutableArray所有方法代理集合(类名是NSKeyValueFastMutableArray2),那么给这个代理集合发送NSMutableArray的方法,以insertObject:inAtIndex: , removeObjectFromAtIndex: 或者 insertAdIndexes , removeAtIndexes组合的形式调用。还有两个可选实现的接口:replaceOnjectAtIndex:withObject: , replaceAtIndexes:with: 。
如果上步的方法没有找到,则搜索set: 格式的方法,如果找到,那么发送给代理集合的NSMutableArray最终都会调用set:方法。 也就是说,mutableArrayValueForKey:取出的代理集合修改后,用·set:· 重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。
如果上一步的方法还还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),会按_,,的顺序搜索成员变量名,如果找到,那么发送的NSMutableArray消息方法直接交给这个成员变量处理。
如果还是找不到,调用valueForUndefinedKey:
关于mutableArrayValueForKey:的适用场景,我在网上找了很多,发现其一般是用在对NSMutableArray添加Observer上。
如果对象属性是个NSMutableAArray、NSMutableSet、NSMutableDictionary等集合类型时,你给它添加KVO时,你会发现当你添加或者移除元素时并不能接收到变化。因为KVO的本质是系统监测到某个属性的内存地址或常量改变时,会添加上- (void)willChangeValueForKey:(NSString *)key
和- (void)didChangeValueForKey:(NSString *)key方法来发送通知,所以一种解决方法是手动调用者两个方法,但是并不推荐,你永远无法像系统一样真正知道这个元素什么时候被改变。另一种便是利用使用mutableArrayValueForKey:了。
@interface demo : NSObject
@property (nonatomic,strong) NSMutableArray*
@implementation demo
-(id)init{
if (self == [super init]){
_arr = [NSMutableArray new];
[self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@",change);
-(void)dealloc{
[self removeObserver:self forKeyPath:@"arr"]; //一定要在dealloc里面移除观察
-(void)addItem{
[_arr addObject:@"1"];
-(void)addItemObserver{
[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];
-(void)removeItemObserver{
[[self mutableArrayValueForKey:@"arr"] removeLastObject];
demo* d = [demo new];
[d addItem];
[d addItemObserver];
[d removeItemObserver];
17:48:22.675 KVCDemo[] {
indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";
17:48:22.677 KVCDemo[] {
indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
@interface demo : NSObject@property (nonatomic,strong) NSMutableArray* arr;@end@implementation demo-(id)init{&&&&if (self == [super init]){&&&&&&&&_arr = [NSMutableArray new];&&&&&&&&[self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];&&&&}&&&&return self;}-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{&&&&NSLog(@"%@",change);}-(void)dealloc{&&&&[self removeObserver:self forKeyPath:@"arr"]; //一定要在dealloc里面移除观察}-(void)addItem{&&&&[_arr addObject:@"1"];}-(void)addItemObserver{&&&&[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];}-(void)removeItemObserver{&&&&[[self mutableArrayValueForKey:@"arr"] removeLastObject];}@end然后再:demo* d = [demo new];[d addItem];[d addItemObserver];[d removeItemObserver];&打印结果2016-04-18 17:48:22.675 KVCDemo[32647:505864] {&&&&indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";&&&&kind = 2;&&&&new =&&&& (&&&&&&&&1&&&&);}2016-04-18 17:48:22.677 KVCDemo[32647:505864] {&&&&indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";&&&&kind = 3;&&&&old =&&&& (&&&&&&&&1&&&&);}
从上面的代码可以看出,当只是普通地调用[_arr addObject:@"1"]时,Observer并不会回调,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];这样写时才能正确地触发KVO。打印出来的数据中,可以看出这次操作的详情,kind可能是指操作方法(我还不是很确认),old和new并不是成对出现的,当加添新数据时是new,删除数据时是old
而对于无序的容器,可以用下面的方法:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
该方法返回一个可变的无序数组如果调用该方法,KVC的搜索顺序如下
搜索addObjectObject: , removeObject: 或者 add , remove 格式的方法
如果至少找到一个insert方法和一个remove方法,那么同样返回一个可以响应NSMutableSet所有方法代理集合(类名是NSKeyValueFastMutableSet2),那么给这个代理集合发送NSMutableSet的方法,以addObjectObject: , removeObject: 或者 add , remove组合的形式调用。还有两个可选实现的接口:intersect , set: 。
如果reciever是ManagedObject,那么就不会继续搜索。
如果上步的方法没有找到,则搜索set: 格式的方法,如果找到,那么发送给代理集合的NSMutableSet最终都会调用set:方法。 也就是说,mutableSetValueForKey取出的代理集合修改后,用set: 重新赋值回去去。这样做效率会低很多。所以推荐实现上面的方法。
如果上一步的方法还还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),会按_,,的顺序搜索成员变量名,如果找到,那么发送的NSMutableSet消息方法直接交给这个成员变量处理。
如果还是找不到,调用valueForUndefinedKey:
可见,除了检查reciever是ManagedObject以外,其搜索顺序和mutableArrayValueForKey基本一至,
同样,它们也有对应的keyPath版本
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyP
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyP
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
iOS5和OSX10.7以后还有个mutableOrdered版本
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key
这两种KVC的用法我还不是清楚,目前只能找到用于KVO的例子。如果有读者能在项目中用到,希望可以告诉我。
当对NSDictionary对象使用KVC时,valueForKey:的表现行为和objectForKey:一样。所以使用valueForKeyPath:用来访问多层嵌套的字典是比较方便的。
KVC里面还有两个关于NSDictionary的方法
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedV
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
dictionaryWithValuesForKeys:是指输入一组key,返回这组key对应的属性,再组成一个字典。
setValuesForKeysWithDictionary是用来修改Model中对应key的属性。下面直接用代码会更直观一点
Address* add = [Address new];
add.country = @"China";
add.province = @"Guang Dong";
add.city = @"Shen Zhen";
add.district = @"Nan Shan";
NSArray* arr = @[@"country",@"province",@"city",@"district"];
NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把对应key所有的属性全部取出来
NSLog(@"%@",dict);
NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};
[add setValuesForKeysWithDictionary:modifyDict];
//用key Value来修改Model的属性
NSLog(@"country:%@
province:%@ city:%@",add.country,add.province,add.city);
//打印结果
11:54:30.846 KVCDemo[] {
city = "Shen Zhen";
country = C
district = "Nan Shan";
province = "Guang Dong";
11:54:30.847 KVCDemo[] country:USA
province:california city:Los angle
123456789101112131415161718192021
Address* add = [Address new];add.country = @"China";add.province = @"Guang Dong";add.city = @"Shen Zhen";add.district = @"Nan Shan";NSArray* arr = @[@"country",@"province",@"city",@"district"];NSDictionary* dict = [add dictionaryWithValuesForKeys:arr]; //把对应key所有的属性全部取出来NSLog(@"%@",dict);&NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};[add setValuesForKeysWithDictionary:modifyDict];&&&&&&&&&&&&//用key Value来修改Model的属性NSLog(@"country:%@&&province:%@ city:%@",add.country,add.province,add.city);&//打印结果2016-04-19 11:54:30.846 KVCDemo[6607:198900] {&&&&city = "Shen Zhen";&&&&country = China;&&&&district = "Nan Shan";&&&&province = "Guang Dong";}2016-04-19 11:54:30.847 KVCDemo[6607:198900] country:USA&&province:california city:Los angle
打印出来的结果完全符合预期。
KVC的内部实现机制
前面我们对析了KVC是怎么搜索key的。所以如果明白了key的搜索顺序,是可以自己写代码实现KVC的。在考虑到集合和keyPath的情况下,KVC的实现会比较复杂,我们只写代码实现最普通的取值和设值即可。
@interface NSObject(MYKVC)
-(void)setMyValue:(id)value forKey:(NSString*)
-(id)myValueforKey:(NSString*)
@implementation NSObject(MYKVC)
-(void)setMyValue:(id)value forKey:(NSString *)key{
if (key == nil || key.length == 0) {
if ([value isKindOfClass:[NSNull class]]) {
[self setNilValueForKey:key]; //如果需要完全自定义,那么这里需要写一个setMyNilValueForKey,但是必要性不是很大,就省略了
if (![value isKindOfClass:[NSObject class]]) {
@throw @"must be s NSobject type";
NSString* funcName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(funcName)]) {
[self performSelector:NSSelectorFromString(funcName) withObject:value];
BOOL flag =
Ivar* vars = class_copyIvarList([self class], &count);
for (NSInteger i = 0; i
12345678910111213141516171819202122232425262728
@interface NSObject(MYKVC)-(void)setMyValue:(id)value forKey:(NSString*)key;-(id)myValueforKey:(NSString*)key;&@end@implementation NSObject(MYKVC)-(void)setMyValue:(id)value forKey:(NSString *)key{&&&&if (key == nil || key.length == 0) {&&&&&&&&return;&&&&}&&&&if ([value isKindOfClass:[NSNull class]]) {&&&&&&&&[self setNilValueForKey:key]; //如果需要完全自定义,那么这里需要写一个setMyNilValueForKey,但是必要性不是很大,就省略了&&&&&&&&return;&&&&}&&&&if (![value isKindOfClass:[NSObject class]]) {&&&&&&&&@throw @"must be s NSobject type";&&&&&&&&return;&&&&}&&&&&NSString* funcName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];&&&&if ([self respondsToSelector:NSSelectorFromString(funcName)]) {&&&&&&&&[self performSelector:NSSelectorFromString(funcName) withObject:value];&&&&&&&&return;&&&&}&&&&unsigned int count;&&&&BOOL flag = false;&&&&Ivar* vars = class_copyIvarList([self class], &count);&&&&for (NSInteger i = 0; i
上面就是自己写代码实现KVC的部分功能。其中我省略了自定义KVC错误方法,省略了部分KVC搜索key的步骤,但是逻辑是很清晰明了的,后面的测试也符合预期。当然这只是我自己实现KVC的思路,Apple也许并不是这么做的。
KVC的正确性验证
KVC提供了属性值,用来验证key对应的Value是否可用的方法
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outE
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
这个方法的默认实现是去探索类里面是否有一个这样的方法:-(BOOL)validate:error:如果有这个方法,就调用这个方法来返回,没有的话就直接返回YES
@implementation Address
-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{
//在implementation里面加这个方法,它会验证是否设了非法的value
NSString* country = *
country = country.capitalizedS
if ([country isEqualToString:@"Japan"]) {
return NO;
//如果国家是日本,就返回NO,这里省略了错误提示,
return YES;
id value = @"japan";
NSString* key = @"country";
BOOL result = [add validateValue:&value forKey:key error:&error]; //如果没有重写-(BOOL)-validate:error:,默认返回Yes
if (result) {
NSLog(@"键值匹配");
[add setValue:value forKey:key];
NSLog(@"键值不匹配"); //不能设为日本,基他国家都行
NSString* country = [add valueForKey:@"country"];
NSLog(@"country:%@",country);
//打印结果
14:55:12.055 KVCDemo[867:58871] 键值不匹配
14:55:12.056 KVCDemo[867:58871] country:China
1234567891011121314151617181920212223242526
@implementation Address-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{&&//在implementation里面加这个方法,它会验证是否设了非法的value&&&&NSString* country = *value;&&&&country = country.capitalizedString;&&&&if ([country isEqualToString:@"Japan"]) {&&&&&&&&return NO;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //如果国家是日本,就返回NO,这里省略了错误提示,&&&&}&&&&return YES;}@endNSError* error;id value = @"japan";NSString* key = @"country";BOOL result = [add validateValue:&value forKey:key error:&error]; //如果没有重写-(BOOL)-validate:error:,默认返回Yesif (result) {&&&&NSLog(@"键值匹配");&&&&[add setValue:value forKey:key];}else{&&&&NSLog(@"键值不匹配"); //不能设为日本,基他国家都行}NSString* country = [add valueForKey:@"country"];NSLog(@"country:%@",country);//打印结果 2016-04-20 14:55:12.055 KVCDemo[867:58871] 键值不匹配2016-04-20 14:55:12.056 KVCDemo[867:58871] country:China
如上面的代码,当开发者需要验证能不能用KVC设定某个值时,可以调用validateValue: forKey:这个方法来验证,如果这个类的开发者实现了-(BOOL)validate:error:这个方法,那么KVC就会直接调用这个方法来返回,如果没有,就直接返回YES,注意,KVC在设值时不会主动去做验证,需要开发者手动去验证。所以即使你在类里面写了验证方法,但是KVC因为不会去主动验证,所以还是能够设值成功。
KVC在iOS开发中是绝不可少的利器,这种基于运行时的编程方式极大地提高了灵活性,简化了代码,甚至实现很多难以想像的功能,KVC也是许多iOS开发黑魔法的基础。下面我来列举iOS开发中KVC的使用场景
动态地取值和设值
利用KVC动态的取值和设值是最基本的用途了。相信每一个iOS开发者都能熟练掌握,
用KVC来访问和修改私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的,请参考本文前面的Dog类的例子。
Model和字典转换
这是KVC强大作用的又一次体现,请参考我写的,里面
充分地运用了KVC和Objc的runtime组合的技巧,只用了短短数行代码就是完成了很多功能。
修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些空间的API,这样我们就无法正常地访问和修改这些控件的样式。而KVC在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField中的placeHolderText了。
下面演示如果修改placeHolder的文字样式。这里的关键点是如果获取你要修改的样式的属性名,也就是key或者keyPath名。
修改placeHolder的样式
一般情况下可以运用runtime来获取Apple不想开放的属性名
let count:UnsafeMutablePointer =
UnsafeMutablePointer()
var properties = class_copyIvarList(UITextField.self, count)
while properties.memory.debugDescription !=
let t = ivar_getName(properties.memory)
let n = NSString(CString: t, encoding: NSUTF8StringEncoding)
//打印出所有属性,这里我用了Swift语言
properties = properties.successor()
//上面省略了部分属性
Optional(_disabledBackgroundView)
Optional(_systemBackgroundView)
Optional(_floatingContentView)
Optional(_contentBackdropView)
Optional(_fieldEditorBackgroundView)
Optional(_fieldEditorEffectView)
Optional(_displayLabel)
Optional(_placeholderLabel)
//这个正是我想要修改的属性。
Optional(_dictationLabel)
Optional(_suffixLabel)
Optional(_prefixLabel)
Optional(_iconView)
//下面省略了部分属性
1234567891011121314151617181920212223
let count:UnsafeMutablePointer =&&UnsafeMutablePointer()var properties = class_copyIvarList(UITextField.self, count)while properties.memory.debugDescription !=&&"0x0000"{&&&&let t = ivar_getName(properties.memory)&&&&let n = NSString(CString: t, encoding: NSUTF8StringEncoding)&&&&print(n)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //打印出所有属性,这里我用了Swift语言&&&&properties = properties.successor()}&//上面省略了部分属性Optional(_disabledBackgroundView)Optional(_systemBackgroundView)Optional(_floatingContentView)Optional(_contentBackdropView)Optional(_fieldEditorBackgroundView)Optional(_fieldEditorEffectView)Optional(_displayLabel)Optional(_placeholderLabel)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& //这个正是我想要修改的属性。Optional(_dictationLabel)Optional(_suffixLabel)Optional(_prefixLabel)Optional(_iconView)//下面省略了部分属性
可以从里面看到其他还有很多东西可以修改,运用KVC设值可以获得自己想要的效果。
Apple对KVC的valueForKey:方法作了一些特殊的实现,比如说NSArray和NSSet这样的容器类就实现了这些方法。所以可以用KVC很方便地操作集合
用KVC实现高阶消息传递
当对容器类使用KVC时,valueForKey:将会被传递给容器中的每一个对象,而不是容器本身进行操作。结果会被添加进返回的容器中,这样,开发者可以很方便的操作集合来返回另一个集合。
NSArray* arrStr = @[@"english",@"franch",@"chinese"];
NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString* str
in arrCapStr) {
NSLog(@"%@",str);
NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber* length
in arrCapStrLength) {
NSLog(@"%ld",(long)length.integerValue);
16:29:14.239 KVCDemo[] English
16:29:14.240 KVCDemo[] Franch
16:29:14.240 KVCDemo[] Chinese
16:29:14.240 KVCDemo[] 7
16:29:14.241 KVCDemo[] 6
16:29:14.241 KVCDemo[] 7
12345678910111213141516
NSArray* arrStr = @[@"english",@"franch",@"chinese"];NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];for (NSString* str&&in arrCapStr) {&&&&NSLog(@"%@",str);}NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];for (NSNumber* length&&in arrCapStrLength) {&&&&NSLog(@"%ld",(long)length.integerValue);}打印结果2016-04-20 16:29:14.239 KVCDemo[1356:118667] English2016-04-20 16:29:14.240 KVCDemo[1356:118667] Franch2016-04-20 16:29:14.240 KVCDemo[1356:118667] Chinese2016-04-20 16:29:14.240 KVCDemo[1356:118667] 72016-04-20 16:29:14.241 KVCDemo[1356:118667] 62016-04-20 16:29:14.241 KVCDemo[1356:118667] 7
方法capitalizedString被传递到NSArray中的每一项,这样,NSArray的每一员都会执行capitalizedString并返回一个包含结果的新的NSArray。从打印结果可以看出,所有String都成功以转成了大写。
同样如果要执行多个方法也可以用valueForKeyPath:方法。它先会对每一个成员调用 capitalizedString方法,然后再调用length,因为lenth方法返回是一个数字,所以返回结果以NSNumber的形式保存在新数组里。
用KVC中的函数操作集合
KVC同时还提供了很复杂的函数,主要有下面这些
①简单集合运算符
简单集合运算符共有@avg, @count ,
,@sum5种,都表示啥不用我说了吧, 目前还不支持自定义。
@interface Book : NSObject
@property (nonatomic,copy)
@property (nonatomic,assign)
@implementation Book
Book *book1 = [Book new];
book1.name = @"The Great Gastby";
book1.price = 22;
Book *book2 = [Book new];
book2.name = @"Time History";
book2.price = 12;
Book *book3 = [Book new];
book3.name = @"Wrong Hole";
book3.price = 111;
Book *book4 = [Book new];
book4.name = @"Wrong Hole";
book4.price = 111;
NSArray* arrBooks = @[book1,book2,book3,book4];
NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
NSLog(@"sum:%f",sum.floatValue);
NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
NSLog(@"avg:%f",avg.floatValue);
NSNumber* count = [arrBooks valueForKeyPath:@"@count"];
NSLog(@"count:%f",count.floatValue);
NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];
NSLog(@"min:%f",min.floatValue);
NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];
NSLog(@"max:%f",max.floatValue);
16:45:54.696 KVCDemo[] sum:256.000000
16:45:54.697 KVCDemo[] avg:64.000000
16:45:54.697 KVCDemo[] count:4.000000
16:45:54.697 KVCDemo[] min:12.000000
16:45:54.697 KVCDemo[] max:111.000000
12345678910111213141516171819202122232425262728293031323334353637383940
@interface Book : NSObject@property (nonatomic,copy)&&NSString* name;@property (nonatomic,assign)&&CGFloat price;@end@implementation Book@end&&Book *book1 = [Book new];book1.name = @"The Great Gastby";book1.price = 22;Book *book2 = [Book new];book2.name = @"Time History";book2.price = 12;Book *book3 = [Book new];book3.name = @"Wrong Hole";book3.price = 111;&Book *book4 = [Book new];book4.name = @"Wrong Hole";book4.price = 111;&NSArray* arrBooks = @[book1,book2,book3,book4];NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];NSLog(@"sum:%f",sum.floatValue);NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];NSLog(@"avg:%f",avg.floatValue);NSNumber* count = [arrBooks valueForKeyPath:@"@count"];NSLog(@"count:%f",count.floatValue);NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];NSLog(@"min:%f",min.floatValue);NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];NSLog(@"max:%f",max.floatValue);&打印结果2016-04-20 16:45:54.696 KVCDemo[1484:127089] sum:256.0000002016-04-20 16:45:54.697 KVCDemo[1484:127089] avg:64.0000002016-04-20 16:45:54.697 KVCDemo[1484:127089] count:4.0000002016-04-20 16:45:54.697 KVCDemo[1484:127089] min:12.0000002016-04-20 16:45:54.697 KVCDemo[1484:127089] max:111.000000
②对象运算符
比集合运算符稍微复杂,能以数组的方式返回指定的内容,一共有两种:
@distinctUnionOfObjects
@unionOfObjects
它们的返回值都是NSArray,区别是前者返回的元素都是唯一的,是去重以后的结果;后者返回的元素是全集。
用法如下:
NSLog(@"distinctUnionOfObjects");
NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
for (NSNumber *price in arrDistinct) {
NSLog(@"%f",price.floatValue);
NSLog(@"unionOfObjects");
NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
for (NSNumber *price in arrUnion) {
NSLog(@"%f",price.floatValue);
16:47:34.490 KVCDemo[] distinctUnionOfObjects
16:47:34.490 KVCDemo[] 111.000000
16:47:34.490 KVCDemo[] 12.000000
16:47:34.490 KVCDemo[] 22.000000
16:47:34.490 KVCDemo[] unionOfObjects
16:47:34.490 KVCDemo[] 22.000000
16:47:34.490 KVCDemo[] 12.000000
16:47:34.490 KVCDemo[] 111.000000
16:47:34.490 KVCDemo[] 111.000000
1234567891011121314151617181920
NSLog(@"distinctUnionOfObjects");NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];for (NSNumber *price in arrDistinct) {&&&&NSLog(@"%f",price.floatValue);}NSLog(@"unionOfObjects");NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];for (NSNumber *price in arrUnion) {&&&&NSLog(@"%f",price.floatValue);}&2016-04-20 16:47:34.490 KVCDemo[1522:128840] distinctUnionOfObjects2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] unionOfObjects2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.0000002016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000
前者会将重复的价格去除后返回所有价格,后者直接返回所有的图书价格。(因为只返回价格,没有返回图书,感觉用处不大。)
③Array和Set操作符
这种情况更复杂了,说的是集合中包含集合的情况,我们执行了如下的一段代码:
@distinctUnionOfArrays
@unionOfArrays
@distinctUnionOfSets
@distinctUnionOfArrays:该操作会返回一个数组,这个数组包含不同的对象,不同的对象是在从关键路径到操作器右边的被指定的属性里
@unionOfArrays 该操作会返回一个数组,这个数组包含的对象是在从关键路径到操作器右边的被指定的属性里和@distinctUnionOfArrays不一样,重复的对象不会被移除
@distinctUnionOfSets 和@distinctUnionOfArrays类似。因为Set本身就不支持重复。
你没看错,KVO是基于KVC实现的。那么是怎么用KVC实现KVO的呢,请期待下章。
本文全方位介绍了KVC的原理和各种用法。相信读者看完后对会KVC会有更完全的理解,也会在项目里更好的运用KVC。其实这里面所有的东西在官方文档里都有详细的讲解说明。只不过全是英文的,我也看过几遍,但是英语不好会看得很吃力,比如官方在介绍@distinctUnionOfArrays时的那句话我想了很好久也不是很明白,而且官方的示例代码也做得不够好,所以很难找出某些功能的适用场景。但我还是推荐各位开发者能够学好英语去看官方文档。再结合StackOverFlow和Google。真的可以解决绝大多数开发中碰到的难题了。这篇文章就到这里,下篇我向大家介绍KVO。
可能感兴趣的话题
关于iOS频道
iOS频道分享iOS和Swift开发,应用设计和推广,iOS相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2016 伯乐在线

我要回帖

更多关于 troller 的文章

 

随机推荐