Coco学习之KVC

来源:互联网 发布:淘宝人工售后客服 编辑:程序博客网 时间:2024/06/04 23:26

首先,什么是KVC,KVC的全称是KeyValueCoding,也叫键值编码。KVC的用途是给使用者提供一种通过属性名称获取和改变属性值的方法,属性名称就是Key,属性的值就是Value。这里,属性的名称是以字符串变量的方式提供的,能够根据字符串变量来调用相应的方法,就为程序提供的很大的动态特性,下面我们将看到这种动态性。KVC是Objective C中一个非常基础,非常重要的机制,Cocoa绑定,Core Data,AppleScript等关键技术都是基于KVC实现的(还有KVO,键值观察)。

下面举个简单的例子说明KVC是如何使用的,假如有一个这样的类:

     @interface Person : NSObject     @property NSString* name;     @property int* age;     @end

可以这样使用KVC:

     Person* p = [[Person alloc] init];    [p setValue:@"Lucy" forKey:@"name"];    [p setValue:@12 forKey:@"age"];        NSLog(@"%@'s age is %d!\n", [p valueForKey:@"name"], [p valueForKey:@"age"]);

输出的结果为:

2014-01-12 09:05:10.366 KVCExmples[1463:303] Lucy's age is 12!

使用了SetValue:forKey设置person的姓名和年龄,使用valueForKey获取Person的姓名和年龄。这个例子并没有直观的显示处KVC的威力,可以使用p.name=@“Lucy”; p.age=12; 来达到同样的目的。但是,当只有在运行时才能知道属性的名字的时候,KVC就派上用场了。比如,使用一个table显示一组Person的姓名和年龄,数据源应该是这样的:
   -(id)tableView:(NSTableView*) tableView objectValueForTableColumn:(id)colum row:(NSInteger)row{    Person* p = [personArray objectAtIndex:row];        if([[colum identifier] isEqualToString:@"name"])    {        return p.name;    }    else if([[colum identifier] isEqualToString:@"age"])    {        return p.age;    }        return nil;}
而使用KVC可以大大简化端代码:

   Person* p = [personArray objectAtIndex:row];   return [p valueForKey:[colum identifier]];

初步了解了KVC的使用之后,下面将介绍KVC的基本用法。上边的例子中使用了-setValue:forKey:和valueForKey来获取和设置Person实例的name属性和age属性。为我们扩充一下上边的Person,增加了Person的department和creditCards属性。Person类的设计反映了这样一个模型,一个Person只能属于一个Department,一个Person可以有多个CreditCard。用术语来说,department是Person的一个to-one的属性,creditCards是一个to-many的属性。

     @interface Department : NSObject     @property NSString* name;     @property NSString* decription;     @end     @interface CreditCard : NSObject     @property NSString* cardNumber;     @property int limit;     @end     @interface Person : NSObject     @property NSString* name;     @property int* age;     @property Department* department;     @property NSMutableArray* creditCards;     @end
KVC支持Key路径,通过使用 -setValue:forKeyPath:, -valueForKeyPath: 我们可以直接访问或者设置person所属的department的名称属性,路径的使用是这样的:

     [p setValue:@"IT" forKeyPath:@"department.name"];     NSLog(@"my department name is %@\n", [p valueForKeyPath:@"department.name"]);

需要注意的是,如果路径中的某一层级不支持KVC,将抛出valueForUndefinedKey异常。关于支持KVC的条件,接下来将介绍。如果路径中某一层级本身是to-many关系,则返回一个数组。
最佳实践,除非确定Key的路径为一级的,否则应该使用-setValue:forPath和-valueForPath。
  KVC还支持批量调用,使用-dictionaryWithValueForKeys: 和-setValueForKeyWithDictionary我们可以一次性设置或者取出多个属性值。看例子:
     [p setValue:@"IT" forKeyPath:@"department.name"];     NSLog(@"my department name is %@\n", [p valueForKeyPath:@"department.name"]);         NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:                          @"xiaohua", @"name", @15, @"age", nil];         [p setValuesForKeysWithDictionary:dict];         NSArray* array = [NSArray arrayWithObjects:@"name", @"age", nil];     NSDictionary* dictRet =[p dictionaryWithValuesForKeys:array];         NSLog(@"name is: %@, age is %@\n", [dictRet valueForKey:@"name"], [dictRet valueForKey:@"age"]);
输出为:2014-01-12 15:16:16.763 KVCExmples[3349:303] name is: xiaohua, age is 15

对于to-many关系的creditCard,使用
      [p setValue:cards forKey:@"creditCards"];      [p valueForKey:@"creditCards”];
creditCards看作一个to-one属性来使用。苹果文档上说,在to-many属性上使用-valueForKey返回的是一个不可变的集合,但是我测试可以将其转换为可变集合并修改的。不清楚怎么回事。
    NSMutableArray* cs = [p valueForKey:@"creditCards"];        for (CreditCard* c in cs)    {        NSLog(@"number:%@, limit:%d", c.cardNumber, c.limit);    }    [cs addObject:card2];        NSMutableArray* cs2 = [p valueForKey:@"creditCards"];    for (CreditCard* c in cs2)    {        NSLog(@"number:%@, limit:%d", c.cardNumber, c.limit);    }
结果为:
2014-01-13 08:17:17.507 KVCExmples[551:303] number:123, limit:10000
2014-01-13 08:17:17.508 KVCExmples[551:303] number:23456, limit:2134  
2014-01-13 08:17:17.509 KVCExmples[551:303] number:123, limit:10000
2014-01-13 08:17:17.510 KVCExmples[551:303] number:23456, limit:2134
2014-01-13 08:17:17.510 KVCExmples[551:303] number:23456, limit:2134
可以看到确实得到了修改。不知道是怎么回事。
  但是还是建议使用官方文档建议的-mutableArrayValueForKey,-mutableSetValueForKey,返回一个可变集合的代理。同样也有对应的-mutableArrayValueForKeyPath和-mutableSetValueForKeyPath.


KVC提供的方法是由NSKeyValueCoding协议指定的,NSObject遵守了这个协议,因此所有从NSObject中直接或者间接派生的类都已经遵守了这个协议。但是仅仅遵守协议是不够的,使用KVC还必须遵守一些约定,才能让类的一个属性支持KVC调用。如果调用传入了一个类不支持的Key,就会得到一个运行时错误:this class is not key value coding-compliant for the key XXX下面就介绍遵守KVC约定的类必须满足的条件。
对于to-one属性,KVC约定要求:
     (1)实现-<Key>方法,如果是BOOL属性,-is<key>也可以; 或者,有成员变量叫做<Key>或者_<Key>;
     (2)如果属性可读,需要提供-set<Key>方法
     (3)如果属性支持验证,必须实现-validate<Key>:error:方法
       
下面解释一下,在Person类中,有一个@property NSString* name;我们知道,@propery关键字自动帮我们实现了-setName和name方法,因此name属性是遵守KVC约定的(同理,如果实现了-setName和-name方法,我们也自动有了一个属性name)。如果Person没有一个@property叫做name,也可以通过下列方式满足KVC约定:
     @implementation Person     {         NSString* _name;     }     @end
通过这种方式,可以在不暴露_name属性的前提下实现KVC。
    这里有一个特殊的情况,age。KVC方法valueForKey,setValue:forKey中的value参数都是对象类型,而age的属性是值类型。不过不用担心,KVC会自动进行装箱和拆箱操作,特殊的是,如果给value传入nil,将会得到运行时异常:Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[<Person 0x10010a390> setNilValueForKey]: could not set nil as the value for the key age. 为了避免这种异常,应该在Person中重写setNilValueForKey:方法。setNilValueForKey方法也是NSKeyValueCoding中的方法,在nil被当作参数的时候调用,默认的,如果给一个值属性传入一个nil,抛出NSInvalidArgumentException,我们可以重写这个方法,改变其行为,比如不抛出异常,而是给age赋值为0
     - (void)setNilValueForKey:(NSString *)key     {         if([key isEqualToString:@"age"])         {             [self setValue:[NSNumber numberWithFloat:0.0] forKey:@"age"];         }         else         {             [super setNilValueForKey:key];         }     }

对于to-many属性,分为有序和无序集合两种,对于有序集合,KVC约定要求:
  (1)实现-<Key>方法,返回一个array。或者有一个成员变量叫做<Key>或者_<Key>.
  (2)或者实现下列方法组合:-countOf<Key>,-objectIn<Key>AtIndex:,和-<Key>AtIndexes:后两者选一或者都实现。
  (3)出于性能考虑,也可以实现-get<Key>:range:
  如果属性是可变的,还应该满足下列条件:
  (4)-insertObject:in<Key>AtIndex:, -insert<Key>:atIndex:,二者选一或者都实现。
  (5)-removeObjectFrom<Key>AtIndex:, -remove<Key>AtIndex:,二者选一或者都实现。
  (6)同样,出于性能考虑,应该实现-replaceObjectIn<Key>AtIndex:withObject:或者-replace<Key>AtIndex:with<Key>:.
对于无序集合,KVC要求:
  (1)实现-<Key>方法,返回一个set。或者有一个成员变量叫做<Key>或者_<Key>
  (2)或者实现方法组合:-countOf<key>:, -enumeratorOf<Key>:和memberOfKey:
  如果属性是可变的,还应该满足下列条件:
  (3)-add<Key>Object:,-add<Key>二者选一或者都实现。
  (4)-remove<Key>Object, -remove<Key>二者选一,或者都实现。
  (5)出于性能考虑,应该实现-intersect<Key>或者—set<Key>


0 0
原创粉丝点击