Key-Value Coding; Key-Value Observing

本文通过创建Objective-C应用,详细介绍了Cocoa编程中的Key-Value Coding(KVC)和Key-Value Observing(KVO)机制,包括如何使用KVC设置和获取变量值,以及如何实现变量值的观察和同步。

 

 

Chapter 7. Key-Value Coding; Key-Value Observing

摘自:《Cocoa Programming for Mac OS X, 3rd》 作者:Aaron Hillegass

 

Key-value coding (KVC) is a mechanism that allows you to set and get the value of a variable by its name. The name is simply a string, but we refer to that name as a key. So, for example, imagine that you have a class called Student that has an instance variable called firstName of typeNSString:

@interface Student : NSObject
{
    NSString *firstName;
}
...
@ends

If you had an instance of Student, you could set its firstName like this:

Student *s = [[Student alloc] init];
[s setValue:@"Larry" forKey:@"firstName"];

You could read the value of its firstName like this:

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

The methods setValue:forKey: and valueForKey: are defined in NSObject. Even though this doesn't look like rocket science, the ability to read and set a variable by its name is really powerful. The rest of this chapter will be a simple example that should illustrate some of that power.

Key-Value Coding

In Xcode, create a new project of type Cocoa Application. Name the project KVCFun. In the project, create a new file of type Objective-C Class. Name the class AppController.

At this point in our exploration of key-value coding, you simply need an instance of AppController to be created with the MainMenu.nib file read in. Drag out a custom object and set its class to be AppController (Figure 7.1).

 

Figure 7.1. Create AppController

 

 

 


Save the nib file.

Back in Xcode, open AppController.h, and add an instance variable called fido of type int:

@interface AppController : NSObject
{
    int fido;
}
@end

In AppController.m, you are going to create an init method that sets and reads fido by using key-value coding. This is a bit silly because it is going to be a long-winded way to get a simple result. This is designed to be illustrative rather than practical.

What makes the method so long-winded is that the key-value coding methods work with objects, so instead of passing an int, you will need to create an NSNumber. Add this method to AppController.m:

- (id)init
{
    [super init];
    [self setValue:[NSNumber numberWithInt:5]
            forKey:@"fido"];
    NSNumber *n = [self valueForKey:@"fido"];
    NSLog(@"fido = %@", n);
    return self;
}

The key-value coding mechanism will automatically convert the NSNumber to an int before using it to set the value of fido. Build and run the application, but don't expect much. When the blank window appears, "fido = 5" will be logged to the console.

If you have accessor methods for getting and setting fido, they will be used. You must, however, give them the correct names. The getter must be called fido, and the setter must be called setFido:. Note that this is more than simply a convention; if you give your accessors nonstandard names, they will not get called by the key-value coding methods. Add fido and setFido: to AppController.m:

- (int)fido
{
    NSLog(@"-fido is returning %d", fido);
    return fido;
}

- (void)setFido:(int)x
{
    NSLog(@"-setFido: is called with %d", x);
    fido = x;
}

Declare these methods in AppController.h:

- (int)fido;
- (void)setFido:(int)x;

Build and run the application. Note that your accessor methods are being called.

 

Bindings

Many graphical objects in Cocoa have bindings. When you bind a key, such as fido, to an attribute of the graphical object (like its value or its font color), the view will automatically keep those in sync. You are going to add a slider, bind its value to fido, and see how it uses key-value coding to them in sync.

Open MainMenu.nib. Drop a slider on the window. In the Attributes Inspector, make the slider Continuous (Figure 7.2).

 

Figure 7.2. Make Slider Continuous

 

 

 


In the Bindings Inspector, bind the value of the slider to the fido key of the instance of AppController (Figure 7.3).

 

Figure 7.3. Bind Value of Slider to fido

 

 

 


Build and run the application. Note that the slider uses valueForKey: to get its initial value, which triggers your fido method. As you move the slider, it calls setValue:forKey: to update the fido variable, which triggers your setFido: method.

 

 

 

Key-Value Observing

What happens if fido is changed by something other than the slider? How would the slider know that it has a new value?

When the slider is created, it tells the AppController that it is observing its fido key. Whenever the value of fido is changed by the accessor methods or by key-value coding, the AppController sends a message to the slider, notifying it that fido has changed.

Open MainMenu.nib again. Add a Label text field to the window, and bind its value to AppController's fido key (Figure 7.4).

 

Figure 7.4. Bind Value of Text Field to fido

 

 

 


Build and run the app. Note that when you move the slider, setFido: is called. This notifies the text field that fido has changed. The text field uses valueForKey: to get the new value of fido. Thus, you see the fido method getting called.

 

 

 

Properties and Their Attributes

As you can guess, we spend a lot of time calling accessor methods. In Objective-C 2.0, Apple gives programmers the option of calling accessors by using dot notation. If you have a pointer rover to an object with a getter method rex, you can call it like this:

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

To call setRex:, you could do this:

rover.rex = [NSDate date];

Overall, I think that this is a rather silly addition to the language since we already had a syntax for sending messages. So I won't be using it in this book.

What about writing the accessor methods? If your object has 12 instance variables, do you need to write 12 setters and 12 getters?

@property and @synthesize

With Objective-C 2.0, Apple introduced a very elegant way to eliminate a lot of this code. In the AppController.h file, replace the declaration of the fido and setFido: methods with the declaration of a property:

@interface AppController : NSObject {
    int fido;
}
@property(readwrite, assign) int fido;
@end

This one line is equivalent to declaring setFido: and fido methods.

In AppController.m, you can use @synthesize to implement the accessor methods. Delete your fido and setFido: methods, and replace them with this line:

@synthesize fido;

Note that everything still works. (Naturally, you won't see the log statements anymore.)

Attributes of a Property

In general, the declaration of a property looks like this:

@property (attributes) type name;

The attributes can include readwrite (the default) or readonly. A property marked readonly gets no setter method.

To describe how the setter method should work, the attributes can also include one of the following: assignretaincopy. Let's look at each in turn:

  • assign (the default) makes a simple assignment happen. assign does not retain the new value. If you are dealing with an object type and you are not using the garbage collector, you probably don't want assign.

  • retain releases the old value and retains the new value. This attribute is used only for Objective-C object types. If you are using the garbage collector, assign and retain are equivalent.

  • copy makes a copy of the new value and assigns the variable to the copy. This attribute is often used for properties that are strings.

Finally, the attributes can also include nonatomic. If your application is multithreaded, it is sometimes important that your setter methods beatomic. That is, the execution of the setter method from one thread will not conflict with the execution of the same setter method on another thread. By default, the @synthesize call will generate accessors with this property. On an application that is not using the garbage collector, this involves using a lock to ensure that only one thread at a time is executing the setter. Creating and using the locks introduces some overhead. If you know that the accessors for a property don't need to be atomic, you can eliminate the overhead by adding nonatomic to the attributes.

 

 

 

For the More Curious: Key Paths

Objects are often arranged in a network. For example, a person might have a spouse who has a scooter that has a model name (Figure 7.7).

 

Figure 7.7. Objects Are a Directed Graph

 


To get the model name of the selected person's spouse's scooter, you can use a key path:

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

We'd say that spouse and scooter are relationships of the Person class and that modelName is an attribute of the Scooter class.

There are also operators that you can include in key paths. For example, if you have an array of Person objects, you could get their averageexpectedRaise by using key paths:

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

Here are some commonly used operators:

@avg
@count
@max
@min
@sum

Now that you know about key paths, you can create bindings programmatically. If you had a text field in which you wanted to show the average expected raise of the arranged objects of an array controller, you could create a binding, like this:

[textField bind:@"value"
       toObject:employeeController
    withKeyPath:@"arrangedObjects.@avg.expectedRaise"
        options:nil];

Of course, it is usually easier to create a binding in Interface Builder.

Use the unbind: method to remove the binding:

[textField unbind:@"value"];
 
  

For the More Curious: Key-Value Observing

How did the text field become an observer of the fido key in the AppController object? When it wakes up from being in the nib, the text field adds itself as an observer. If you wanted to become an observer of this key, your line of code might look something like this:

[theAppController addObserver:self
                   forKeyPath:@"fido"
                      options:NSKeyValueObservingOld
                      context:somePointer];

This method is defined in NSObject. It is how you say, "Hey! Send me a message whenever fido changes." The options and context determine what extra data is sent along with that message when fido changes. The method that is triggered looks like this:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
...
}

The keyPath, in this case, would be @"fido", and the object would be the AppController. The context would be the pointer somePointersupplied as the context when you became an observer. The dictionary change is a collection of key-value pairs that can hold the old value of fidoand/or the new value.

 

 

 

下载代码方式:https://pan.quark.cn/s/e6c2e312b658 在苹果公司的Mac操作系统环境中,当用户尝试安装非原厂驱动程序时,可能会遭遇系统无法正常启动的困境。这种情况常常源于名为.kext的内核扩展驱动程序存在兼容性问题或安装过程中出现失误。这份指南介绍了一种无需重新安装操作系统且能够保护所有用户数据的修复方法,这一方案对于先前许多面临类似挑战的用户而言,曾是极为棘手的情况。文档中提及的“用户模式启动”实际是指单用户模式,这种启动方式仅加载核心系统功能,而忽略图形用户界面及常规应用程序的加载。在单用户模式下,用户能够访问命令行界面,进而执行一系列修复指令。解决此问题的首要环节是验证存储设备是否存在故障,因为这是导致系统无法启动的常见诱因。借助终端指令`/sbin/fsck -f`,可以诊断并纠正文件系统层面的错误。倘若系统在启动过程中检测到文件系统异常,通常会自动执行`fsck`命令,然而,如果系统卡在进度条100%无法继续,手动运行该命令则显得尤为必要。指令`mount -uw /`的功能是将根目录切换为可读写状态,由于系统默认是以只读模式启动的。这一操作的目的是为了在不重新进入正常模式的前提下,对系统进行必要的调整。随后,文档提供了一个关键操作:对存在问题的驱动程序文件进行修改或更名。在Mac系统中,第三方驱动程序一般安装在`/Library/Extensions/`目录下。每个驱动程序都包含一个以.kext为后缀名的文件夹,例如在此案例中的AX88772.kext。通过命令行将故障的.kext文件更名(例如改为.kext.bak),可以临时禁用该驱动程序。这一操作需在命令行环境中完成,首先使用`cd /Library/Exte...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值