`
lovebirdegg
  • 浏览: 174993 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Key-Value Coding. Key-Vaule Observing

阅读更多

Key-value coding (KVC) 是一种机制. 容许我们通过变量的名字来获取和设置变量的值. 变量的名字是简单的字符串, 不过我们称它为"key" . 比如. 我们有个类叫 Student. 它有个类型为NSString的成员变量 firstName.

@interface Student : NSObject

{

   NSString *firstName;

}

...

@ends

如果我们有个Student对象,我们可以这样设置它的firstName

Student *s = [[Student alloc] init];

[s setValue:@"Larry" forKey:@"firstName"];

同时也可以这样获取firstName:

NSString *x = [s valueForKey:@"firstName"];



方 法setValue:forKey: 和 valueForKey定义在NSObject中[又是定义在NSObject中.这表示所有的对象都有这连个方法咯.] 虽然这不象飞机火箭那么复杂.不过这个机制是非常强大有用的. 这一章我们会用一个简单的例子来展示它的威力.



Key-Value Coding



使用XCdoe,新建一个Cocoa Application工程. 命名为KVCFun, 新建一个Objectvie-class文件. 命名为AppController



在Interface Builder, 拖拽一个自定义对象,设置它的类为Appcontroller 如图7.1

第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing

保存nib文件



回到XCode.打开AppController.h, 添加一个int类型的成员变量fido.

@interface AppController : NSObject

{

   int fido;

}

@end



编辑AppController.m, 我们打算创建init方法,通过key-value coding去读取设置变量fido. 当然这看上去有点傻:用复杂的方法去实现一个简单的功能.不过,我们这样做是为了说明问题,而不是实际编程.



这就是那个使用key-value coding的复杂方法. 我们用NSNumber对象来代替int类型.

- (id)init

{

   [super init];

   [self setValue:[NSNumber numberWithInt:5]

           forKey:@"fido"];

   NSNumber *n = [self valueForKey:@"fido"];

   NSLog(@"fido = %@", n);

   return self;

}

key-value 机制会自动把NSNumber转换成int类型去设置fido的值. 编译运行程序,没有什么特别的.我们看到一个空白窗口, 看到"fido=5"打印在cosole里面



假 如我们定义了accessor方法去读取,设置fido.那么它们将被调用. 当然你必须正确的知道accessor方法的名字. getter必须是fido. 而setter必须是setFido: . 注意,这样做不仅仅是因为编码规范. 如果我们的accessor方法名不标准,当使用key-value coding方法时.它们就得不到调用.[很显然这是apple的规定阿.在key-value方法中就是以这样fido 和 setFido的方法名来调用的.如果我们名字取的和它们不一样.当然没有办法调用到了] . 添加fido和setFido:方法

- (int)fido

{

   NSLog(@"-fido is returning %d", fido);

   return fido;

}



- (void)setFido:(int)x

{

   NSLog(@"-setFido: is called with %d", x);

   fido = x;

}

同时在AppController.h中声明它们.

- (int)fido;

- (void)setFido:(int)x;

编译运行程序.我们可以看到accessor方法别调用了





绑定 (Binding)



Cocoa中很多图形对象都支持绑定. 如果我们把一个图形对象的属性(比如颜色, 或是值)和一个key,比如fido,绑定起来.那么,图形对象就可以自动和那个key的值同步. 我们添加一个slider,把它到值和fido绑定起来. 看看它们是怎么同步的.



打开MainMenu.nib. 拖拽一个slider到窗口上. 在Attributes Inspector设置slider为Continuous 如图7.2





第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing





在Bindings Inspector中, 把slider的value和AppController对象的fido key绑定一起.图7.3

第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing

编译运行程序. 注意到,slider调用valueForKey:方法来获取它的初始值, 这个方法将调用我们的fido方法. 当我们拖动slider, 会调用setValue:forKey: 去更新fido的值,这会调用setFido:方法

[当 slider做init时, 它发现自己和AppController的对象绑定了,那么它肯定就保留了AppController对象的指针. 然后调用了appController的 valueForKey:方法.并且参数为key-fido. 从而fido方法得到调用. 而在更新的时候,同样因为绑定.会调用 appController对象的 setValue:forKey:, 参数为key-fido. 这样setFido也被调用了]



Key-Value Observing



如果我们通过其他途径来改变fido值,又会发送什么呢?slider会跟着改变么?它是怎么知道fido的值改变了呢?



当 slider创建的时候, 它告诉了AppController: 我会一直关注这key-fido[用程序语言来讲,应该是slider注册了一个通知,当fido改变时,会通知sldier. 通知会在后面的章节讲到.它和观察者模式一样]. 任何时候通过accesor方法或是key-value coding方法改变了fido的值,AppController都会发送一个消息通知slider:fido改变了



再次打开MainMenu.nib, 添加一个文本框.把它的值和AppController 的key fido绑定.如图 7.4



第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing





编 译运行程序, 我们拖动slider, setFido:方法被调用. 同时会给绑定fido的文本框也发送一个通知告知fido被改变了. 文本框会使用valueForKey:来得到fido的新值. 因此,fido的方法被调用 [建议大家可以把断点设置在accessor方法里面,然后看看调用时的运行堆栈,能看到什么呢?]



观察key



前一节,当使用accessor方法或key-value coding方法去改变key的值,观察者可以自动得到改变的通知. 那假如我们直接修改变量的值又会如何?



打开AppController.h, 声明一个新的action方法

- (IBAction)incrementFido:(id)sender;

打开AppController.m,实现该方法

- (IBAction)incrementFido:(id)sender

{

   fido++;

   NSLog(@"fido is now %d", fido);

}



打开MainMenu.nib, 给窗口添加一个按钮.命名为Increment Fido. Control-drag按钮到AppController对象建立连接. 这个按钮将会触发incrementFido action如图7.5



第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing





我们希望当点击这个按钮是,slider会跟着移动,而文本框也会刷新. 不幸的是.现在还不行. 试试编译运行看看吧



如果我们要支持给变量赋值,我们必须给这个key的观察者发送通知. 修改incrementFido:方法

- (IBAction)incrementFido:(id)sender

{

   [self willChangeValueForKey:@"fido"];

   fido++;

   NSLog(@"fido is now %d", fido);

   [self didChangeValueForKey:@"fido"];

}

编译运行.OK, Increment 按钮可以正常工作了.



还有两个实现方法.第一个,使用key-value coding

- (IBAction)incrementFido:(id)sender

{

   NSNumber *n = [self valueForKey:@"fido"];

   NSNumber *npp = [NSNumber numberWithInt:[n intValue] + 1];

   [self setValue:npp forKey:@"fido"];

}

或者使用accessor方法改变fido

- (IBAction)incrementFido:(id)sender

{

   [self setFido:[self fido] + 1];

}



试试修改,然后编译运行看看



图7.6是我们实现的对象关系图. 注意绑定是使用半个箭头来表示

第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing



Properties 和它们的属性



你可能猜到,我们大量的使用着accessor方法. 从Objective-2.0,apple增加一个新的方法,我们可以通过点号来调用accessor方法. 比如我们有一个对象指针rover,该对象有个getter方法 rex. 那么我们可以这样使用

NSLog(@"Rover's rex is %@", rover.rex);

而要调用setter方法,可以这样

rover.rex = [NSDate date];



在我看来,在已经有发送消息的语法情况下,这是一个愚笨的新特性.所以我不会在这本书中使用这种方法.



编写accessor方法又怎么样?如我对象有12个成员变量,我们需要12个getter和12setter方法么?



-- @property 和 @synthesize

同样在Objective-C2.0中,apple提供了一条非常优雅的方法来节省代码量. 在AppController.h文件. 使用声明一个property来替换fido和setFido:

@interface AppController : NSObject {

   int fido;

}

@property(readwrite, assign) int fido;

@end



这行代码和声明setFido:和fido相同



在AppController.m中, 我们使用@synthesize来实现accessor方法. 删除方法fido和setFido: 用下面的代码代替

@synthesize fido;



恩.所有的都能正常工作(当然,我们看不到那些log了)



-- Property的属性

一般,我们这样来声明一个property

@property (attributes) type name;



attributes可以是 readwrite (默认的) 或是 readonly. 如果使用readonly,那么就只有getter方法.



我们也可以通过:assign, retain,copy属性来指定setter方法的工作方式

.assign(默认) 简单的赋值, assign不会对新的值做retain. 如果使用对象类型的参数,同时没有启用garbage collector.我们不要使用assign

. retain: release旧的值,同时retain新值. 这个属性用在参数为Objective-C对象类型时. 如果启用了garbage collector, 它和assign作用一样. [启用了garbage collector. release和retain什么也不做.]

. copy: 对新值做拷贝,把拷贝赋值给变量. 变量为string时,常用该属性.



最 后,我们还可以使用nonatomic属性. 如果我们的程序是多线程的. 那么让setter方法成为atomic是非常重要的. 也就是说, 不同的线程访问同一个setter方法时,不对产生冲突[线程重入].  如果没有启用garbage collector. 默认的会使用锁机制来保证在同一个时间点,只能由一个线程来调用一个setter方法. 因为创建和使用锁会产生一些资源消耗.所以如果我们能够保证accessor方法不需要atomic. 我们可以使用nonatomic属性来减少这种消耗.



思考: Key Path



通常很多对象会组成对象网络. 比如, 一个人有一个配偶, 而配偶可能有一辆脚踏车, 脚踏车有型号. 如图 7.7

第7章: <wbr>Key-Value <wbr>Coding. <wbr>Key-Vaule <wbr>Observing

我们可以通过key path来得到这个人配偶的脚踏车的型号:

NSString *mn;

mn = [selectedPerson valueForKeyPath:@"spouse.scooter.modelName"];

我们可以说: spouse和scooter是 Person的关联对象.而modelName是Scooter类的属性.



key path支持很多的操作.例如,有一个person序列,我们可以通过key path来求得他们的平均 expectedRaise:

NSNumber *theAverage;

theAverage = [employees valueForKeyPath:@"@avg.expectedRaise"];



以下是一些常用的操作

@avg

@count

@max

@min

@sum



现在,我们大概了解了key path. 我们可以通过程序来进行绑定. 例如, 我们想把一个对象-employeeController中所有的人-arrangedOnjects的期望加薪的平均值显示到一个文本框中. 我们可以使用绑定:

[textField bind:@"value"

      toObject:employeeController

   withKeyPath:@"arrangedObjects.@avg.expectedRaise"

       options:nil];

当然,使用Interface Builder来创建绑定会更简单

使用unbind:来解除绑定:

[textField unbind:@"value"];





思考: Key-Value Observing



文 本框是怎样成为 AppConroller对象 fido key的观察者呢? 当它从nib文件加载时, 文本框把它自己添加成为一个观察者. 如果你想成为一个观察者.代码大概就像这样:[想象一下.可能在NSTextField的 awakFromNib中的代码就是这样写的]

[theAppController addObserver:self

                  forKeyPath:@"fido"

                     options:NSKeyValueObservingOld

                     context:somePointer];

这个方法定义在NSObject中. 意思为:"喂,当fido改变了,通知我一声阿." option和context是随着这个通知fido改变消息一起传送的额外数据.  触发的消息如下[当得到通知,会调用]

- (void)observeValueForKeyPath:(NSString *)keyPath

                     ofObject:(id)object

                       change:(NSDictionary *)change

                      context:(void *)context

{

...

}

在这里.keyPaht为@"fido". object则为AppController. context则为addObserver时给定的somePointer. change为一个NSDictionary对象,保存了原来和新改变的值


 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics