您的位置:首页 > 移动开发 > Objective-C

Objective-C之KVC(键值编码)详解

2015-07-24 07:09 573 查看
一、简单的KVC

最基本的KVC由NSKeyValueCoding协议提供支持的,最基本的操作属性的两个监听方法如下:

setValue:属性值 forKey:属性名,作用是为指定属性设置值。

valueForKey:属性名 ,作用是获取指定的值。

有如下程序:

User.h
#import <Foundation/Foundation.h>

@interface User : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *pass;
@property (nonatomic, strong) NSDate *birth;

@end


在如下程序中测试。

UserTest.m

#import "User.h"

int main(int argc, char * argv[]){

@autoreleasepool{
//创建User对象
User *user = [[User alloc] init];
//使用KVC方式为name,pass和birth赋值
[user setValue:@"孙悟空" forKey:@"name"];
[user setValue:@"500" forKey:@"pass"];
[user setValue:[[NSDate alloc] init] forKey:@"birth"];
//使用KVC方式取值
NSLog(@"user的name为:%@",[user valueForKey:@"name"]);
NSLog(@"user的pass为:%@",[user valueForKey:@"pass"]);
NSLog(@"user的birth为:%@",[user valueForKey:@"birth"]);
}
}

在KVC编程方式中,无论调用setValue:ForKey:方法,还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名。

对于“setValue:属性值 forKey:@”name””代码,底层的执行机制如下:

(1)程序优先调用“setName:属性值”代码通过setter方法完成设置。

(2)如果该类没有setName方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是对_name成员变量赋值。

(3)如果该类既没有setName方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对name成员变量赋值。

(4)如果上面3步没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。

对于“valueForKey:@”name””代码,底层的执行机制如下:

(1)程序优先考虑调用“name;”代码来获取getter方法的返回值。

(2)如果该类没有name方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实现上就是返回_name成员变量的值。

(3)如果该类既没有name方法,也没有定义_name成员变量,那么KVC机制会搜索该类中name的成员变量,无论该成员变量是在接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是返回name成员变量的值。

(4)如果上面3步没有找到,那么系统会执行该对象的valueforUndefinedKey:方法,这个方法的实现就是引发一个异常,这个异常将会导致程序因为异常结束。

如下程序定义了一个Dog类的接口部分。

@interface Dog : NSObject
{
NSString *name;
NSString *_name;
}
@end

上面Dog类的接口部分定义了两个成员变量:name和_name,但是没有定义setName:和name方法。按照前面的介绍,如果程序通过KVC方式来操作name属性,那么程序实际操作的将是_name成员变量。

接下来是Dog的实现部分,实现部分定义了一个age成员变量。

#import “Dog.h”

@implementation Dog
{
int age;
}

下面的程序将会使用KVC方式来操作name和age。

<span style="font-size:14px;">DogTest.m

#import "Dog.h"

int main(int argc, char * argv[]){

<span style="font-size:14px;"><span style="font-family:宋体;"></span></span></span><pre name="code" class="objc">    @autoreleasepool{


//创建Dog对象 Dog*dog = [[Dog alloc] init]; //使用KVC方式为name赋值 //1.setName:方法, 2._name成员变量,3.name成员变量 [dog setValue:@"旺财" forKey:@"name"]; NSLog(@"dog->name:%@",dog->name); NSLog(@"dog->_name为:%@",dog->_name); //使用KVC方式对age赋值,将会导致类实现部分定义的age成员变量被赋值 [dog setValue:[NSNumber
numberWIthInt:2] forKey:@"age"]; NSLog(@"dog的age为:%@",[dog valueForKey:@"age"]);

}

}

上面程序代码[dog setValue:@"旺旺" forKey:@"name"];执行时,程序通过KVC方式对dog的name属性赋值,但由于Dog类并不存在setName方法,因此程序接下来尝试对dog对象_name成员变量赋值,在代码dog->_name时,将可以看到输出“旺财”,但dog->name将输出空。

程序最后通过KVC方式对age属性赋值并访问age属性,由于Dog在类实现部分定义了age成员变量,因此,KVC可以正常工作,编译、运行程序,可以看到如下输出:

dog->name:(null)

dog->_name:旺旺

dog的age:5

二、处理不存在的key

前面已经提到,当使用KVC方式操作属性时,这些属性可能并不存在-既不存在对应的setter、getter方法,也不存在对应的成员变量,KVC将会自动调用setValue: forUndefinedKey:和valueforUndefinedKey:方法-但系统默认实现的这两个方法仅是引发异常,并没有进行特别处理。

例如,如下程序定义了一个Apple类,该类没有定义任何成员变量和方法。

#import <Foundation/Foundation.h>

@interface Apple: NSObject
@end


在AppleTest.m文件中,通过KVC方式来操作Apple对象的name属性。

#import "Apple.h"

int main(int argc, char * argv[]){
@autoreleasepool{
//创建Apple对象
Apple *apple = [[Apple alloc] init];
[apple setValue:@”小苹果” forKey:@”name”];
[apple valueForKey:@”name”];
}
}

上面程序代码操作了Apple对象并不存在的name属性。编译、运行该程序,可以看到程序输出异常:

Terminating apple due to uncaught exception ‘NSUnknownKeyException’,reason:’[<Apple 0x7f8fd0c097b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.’

上面的异常信息提示:程序尝试设置的name不存在,因此,程序引发了NSUnknownKeyException异常。

三、处理nil值

当调用KVC来设置对象的属性时,如果属性的类型是基本类型(如int、float、double),且传入了对应类型的值,那么程序可以正确地进行设置。但如果尝试为基本类型的属性设置为一个nil,则会导致什么结果呢?KVC会把这个nil当成0还是当成1?

如下程序定义了Item类的接口部分Item.h。

#import <Foundation/Foundation.h>

@interface Item: NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int price;
@end


上面Item类中定义了两个属性,其中,name的类型是NSString,这个属性可以接受nil值,price属性的类型是int,它只可能接受int类型的值,不能接受nil值。

使用如下程序尝试将Item对象的name、price设置为nil值,看看程序执行结果。

ItemTest.m

#import "User.h"
int main(int argc, char * argv[]){
<span style="font-family:宋体;">    </span>@autoreleasepool{
//创建Item对象
Item *item = [[Item alloc] init];
//使用KVC方式尝试将name,price设置为nil
[item setValue:nil forKey:@"name"];
[item setValue:nil forKey:@"price"];
NSLog(@"item的name为:%@",[item valueForKey:@"name"]);
NSLog(@"item的price为:%@",[item valueForKey:@"price"]);
}
}

在上面的程序中,尝试将NSString类型的name属性值设置为nil,这是合法的,完全可以正常执行,但是尝试将int类型的price设置为nil时,程序就要有可能引发错误。编译、运行该程序,可以看到如下输出:

Terminating apple due to uncaught exception ‘NSinvalidArgumentException’,reason:’[<Apple 0x7f8601c06ce0> setNilValueForKey:]: could not set nil as the value for the key price.’

从上面的提示结果来看,程序引发了一个NSinvalidArgumentException异常,这是由于int类型的属性不能接受nil值所导致的。上面这段提示信息实际上是由程序中的setNilValueForKey:方法所产生的。也就是说。当程序尝试为某个属性值设置nil值时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。

四、Key路径

KVC除了可操作对象的属性之外,还可操作对象的“复合属性”。所谓‘复合属性’,KVC机制将其称为key路径。比如,Order对象内包含了一个Item类型的item属性,而Item对象又包含了name属性和prie属性,那么KVC可以通过item。Name、item.price这种key路径来支持操作Order对象的item属性的name、price属性。

在KVC协议中操作key路径的方法如下。

setValue: forKeyPath:,根据key路径设置属性值。

valueForKeyPath:,根据key路径获取属性值。

如下程序定义了一个Order(订单)类,该Order类中包含了一个Item类型的属性。

Order.h

#import <Foundation/Foundation.h>
#import “Item.h”

@interface Dog : NSObject
//使用@property定义两个属性
@property (nonatomic, strong) Item *item;
@property (nonatomic, assign) int amount;
-(int) totalPrice;
@end


在测试程序中OrderTest.m

#import "Order.h"

int main(int argc, char * argv[]){

<span style="font-family:宋体;">    </span>@autoreleasepool{
//创建Order对象
Order *order = [[Order alloc] init];
//使用KVC方式为amount赋值
[order setValue:@"12" forKey:@"amount"];
[order setValue:[[Item alloc] init] forKey:@"item"];
//使用setValue: forKeyPath:设置item属性的name和price属性
[order setValue:@"鼠标" forKeyPath:@"item.name"];
[order setValue:[NSNumber numberWithint:20] forKeyPath:@"item.price"];
//使用valueForKeyPath:来获取复合属性值
NSLog(@”订单包含%@个%@,总价为%@”,[order valueForKey:@”amount”], [order valueForKeyPath:@”item.name”], [order valueForKey:@”totalPrice”]);
}
}

上面程序使用了setValue: forKeyPath:和valueForKeyPath:设置复合属性并获取复合属性的值,这样的程序可以直接操作Order对象中的item属性的name属性,也可以直接操作Order对象中的item属性的price属性。

前面介绍了这么多内容,可能会有人感到疑惑:为什么要用KVC方式来操作呢?直接调用对象的setter和getter方法进行操作不可以吗?是不是KVC方式的性能更好呢?

实际上,通过KVC操作对象的性能要比通过setter和getter方法操作的性能更差,使用KVC的优势在于编程更加灵活,更适合提炼一些通用性质的代码。由于KVC方式允许通过字符串形式来操作对昂的属性,这个字符串既可以是变量,也可以是常量,因此具有极高的灵活性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  KVC objective-c ios