Objective-C中的KVC与KVO(上)

来源:互联网 发布:快易数据恢复账号密码 编辑:程序博客网 时间:2024/05/16 15:29

Objective-C中的KVC与KVO是两种比较重要的技术,这里简要介绍一下这两者的使用方法。

一、KVC

《iOS程序开发方法与实践》中介绍了KVC(Key–Value Coding,键值编码)的基本内容。

简要来说,KVC提供了一种在运行时而非编译时动态访问对象属性与成员变量的方式,也就是说,我们可以用字符串的内容作为属性名称或者成员变量名称进行访问。这种特性有些类似于其他高级编程语言中的反射。

举个例子,例如我们创建了一个类ClassA,其中定义了3个属性p1、p2和p3,以及1个实例方法getValueByPropertyName:,代码如下:

@interface ClassA : NSObject@property (nonatomic) int p1;@property (nonatomic) int p2;@property (nonatomic) int p3;- (int)getValueByPropertyName:(NSString*)pName;@end@implementation ClassA@synthesize p1 = _p1, p2 = _p2, p3 = _p3;...- (int)getValueByPropertyName:(NSString*)pName{    if([pName isEqualToString:@"p1"])    {        return self.p1;    }    else if([pName isEqualToString:@"p2"])    {        return self.p2;    }    else if([pName isEqualToString:@"p3"])    {        return self.p3;    }        return 0;}@end

方法getValueByPropertyName:用于通过字符串来访问对应的属性值。如果我们想要向ClassA添加几个属性,那么这个方法就还需要添加几个else if语句。而如果使用KVC就会方便许多,代码如下:

- (int)getValueByPropertyName:(NSString*)pName{    NSNumber* pNumber = [self valueForKey:pName];    return [pNumber intValue];}

这里有几点需要注意,首先这里的valueForKey:方法用于以字符串调用对象的get属性方法,或者读取成员变量的值;与之相对的是setValue:forKey:,它用于以字符串调用对象的set属性方法,或者修改成员变量的值。第二点需要注意的是,对于基本数据类型,KVC方法会对基本数据类型进行封装(基本数据类型封装为NSNumber,其他结构体类型封装为NSValue)。这里的p1、p2、p3定义为int型,而valueForKey:方法返回的是NSNumber对象,需要再调用intValue取出其中的值。setValue:forKey:方法与之类似,接收NSNumber参数。第三,在使用KVC时,如果找不到字符串对应的属性和成员变量时会怎么样?此时会调用valueForUndefinedKey:或者setValue:forUndefinedKey:这两个方法,默认情况下会抛出异常。第四,默认情况下KVC方法能够直接访问类的私有成员变量,如果我们不想这样,可以重写accessInstanceVariablesDirectly方法,并令其返回NO(默认是返回YES)。KVC方法定义在NSKeyValueCoding类别中,该类别附加于NSObject类上,所以所有对象都具有这些方法。第五,在一些特殊的类的对象上调用KVC方法会有特别的效果。对于数组NSArray、集合NSSet,调用valueForKey:会对每个数组和集合成员调用valueForKey:,并返回新的数组或者集合。

在KVC中还有一种常用技术,称为键值链(Key Path)。键值链是用点将若干键相连的字符串,例如“manufacturer.product.name”。通过在对象上调用valueForKeyPath:或者setValue:forKeyPath:,我们就可以在一条语句中接连调用制定的属性。请看下面的例子:

@interface Product : NSObject@property NSString* name;@end@implementation Product@synthesize name = _name;- (void)dealloc{    self.name = nil;    [super dealloc];}@end@interface Manufacturer : NSObject@property Product* product;@end@implementation Manufacturer@synthesize product = _product;-(void)dealloc{    self.product = nil;    [super dealloc];}@endint main(int argc, const char * argv[]){    @autoreleasepool    {        Manufacturer* apple = [[[Manufacturer alloc] init] autorelease];        Product* iPhone4S = [[[Product alloc] init] autorelease];        iPhone4S.name = @"iPhone 4S";        apple.product = iPhone4S;                Manufacturer* micro$oft = [[[Manufacturer alloc] init] autorelease];        Product* win8 = [[[Product alloc] init] autorelease];        win8.name = @"Windows 8";        micro$oft.product = win8;                NSArray* manufacturerArray = [NSArray arrayWithObjects:apple, micro$oft, nil];                NSArray* productNameArray = [manufacturerArray valueForKeyPath:@"product.name"];        NSLog(@"productNameArray:\n%@", productNameArray);            }    return 0;}
首先程序定义了两个类Product与Manufacturer,分别定义商品与制造商。Manufacturer类定义了一个product属性,Product类定义了name属性。在main函数中,我们创建了两个制造商苹果和微软,以及两个商品iPhone4S和Win8,并设置好他们之间的关系。然后程序将两个制造商放入数组中,在数组上调用valueForKeyPath:方法并传入键值链@"product.name"。于是数组首先执行valueForKey:@"product",它会在每个数组成员上调用product属性,获取每个制造商的商品,然后再在每个商品上调用valueForKey:@"name",获取每个制造商的每个商品的名称,并将结果放到另一个数组中。上述程序的输出结果为:
productNameArray:(    "iPhone 4S",    "Windows 8")

使用KVC的好处是非常灵活,但同时也丢失了编译时检查。

在下一篇中,我将会介绍KVO的使用方法。

原创粉丝点击