KVO & KVC使用场景

本文摘自:http://www.cocoachina.com/industry/20140224/7866.html

Key Value Coding

Key Value Coding是cocoa的一个标准组成部分,它能让我们可以通过name(key)的方式访问property, 不必调用明确的property accssor, 如我们有个property叫做foo, 我们可以foo直接访问它,同样我们也可以用KVC来完成[Object valueForKey:@“foo”], 有同学就会问了, 这样做有什么好处呢?主要的好处就是来减少我们的代码量。

下面我们来看看几个例子,就明白了KVO的用法和好处了,假设这样个类叫做People,

@interface People: NSObject 
  
@property (nonatomic, strong) NSString *name; 
@property (nonatomic, strong) NSNumber *age; 
  
@end 

场景1,不直接访问属性,采用valueForKey:来访问,减少代码量
apple 官网的一个例子,当我们需要统计很多People的时候,每一行是一个人的实例,并且有2列属性,name, age, 这时候我们可以会这样做,

- (id)tableView:(NSTableView *)tableview 
      objectValueForTableColumn:(id)column row:(NSInteger)row { 
  
    People *people = [peoleArray objectAtIndex:row]; 
    if ([[column identifier] isEqualToString:@"name"]) { 
        return [people name]; 
    } 
    if ([[column identifier] isEqualToString:@"age"]) { 
        return [people age]; 
    } 
    // And so on. 
} 

同样我们也可以用KVC,帮助我们化简这些if, 因为name, age其实都是property, 我们可以直接通过key来访问,所以整理过后是

People *people = [peopleArray objectAtIndex:row]; 
return [people valueForKey:[column identifier]]; 

场景2,json格式化转model
这下我们有了server, server的某个api(listPeople??), 会返回我们json格式一个数组,里面包含这样dict{name:xx, age:xx}这样的数据, 我们希望用这些dict数据构造出我们的people来,通常我们的做法是,为我们People类写一个static factory方法专门用来处理dict来, 把dict里面的数据取出来, 然后创建个空的People对象,然后依次设置property。然而当这样类似People的与server交互的类多了,我们就要为每个类都要加上这样的wrapper, 是否有种简单办法来设置这样的属性,当然就是我们的KVC了。

-(id) initWithDictionary:(NSMutableDictionary*) jsonObject 
{ 
    if((self = [super init])) 
    { 
        [self init]; 
        [self setValuesForKeysWithDictionary:jsonObject]; 
    } 
    return self; 
} 

setValuesForKeysWithDictionary, 会为我们把和dictionary的key名字相同的class proerty设置上dict中key对应的value, 是不是很方便呀,但是有同学又要问了 如果json里面的某些key就是和object的property名字不一样呢,或者有些server返回的字段是objc保留字如”id”, “description”等, 我们也希望也map dict to object, 这时候我们就需要用上setValue: forUndefinedKey, 因为如果我们不处理这些Undefined Key,还是用setValuesForKeysWithDictionary就会抛出异常。

- (void)setValue:(id)value forUndefinedKey:(NSString *)key 
{ 
    if([key isEqualToString:@"nameXXX"]) 
        self.name = value; 
    if([key isEqualToString:@"ageXXX"]) 
        self.age = value; 
    else 
        [super setValue:value forKey:key]; 
} 

所以只要重载这个方法,就可以处理了那些无法跟property相匹配的key了,默认的实现是抛出一个NSUndefinedKeyException,又有同学发问了如果 这时候server返回的People有了内嵌的json(如Products{product1{count:xx, sumPrice:xx}}, product2{} ….),又该怎么办,能把这个内嵌的json转化成我们的客户端的Product类嘛, 当然可以这时候就需要重载setValue:forKey, 单独处理”Products”这个key, 把它wrapper成我们需要的class

-(void) setValue:(id)value forKey:(NSString *)key 
{ 
  if([key isEqualToString:@"products"]) 
  { 
    for(NSMutableDictionary *productDict in value) 
    { 
      Prodcut *product = [[Product alloc] initWithDictionary:prodcutDict]; 
      [self.products addObject:product]; 
    } 
  } 
} 

场景3,批量处理数组中每个对象的属性
我们需要把一个数组里的People的名字的首字母大写,并且把新的名字存入新的数组, 这时候通常做法会是遍历整个数组,然后把每个People的name取出来,调用 capitalizedString 然后把新的String加入新的数组中。 有了KVC就有了新做法:

[array valueForKeyPath:@"name.capitalizedString"] 

我们看到valueForKeyPath, 为什么用valueForKeyPath, 不用valueForKey, 因为valueForKeyPath可以传递关系,例如这里是每个People的name property的String的capitalizedString property, 而valueForKey不能传递这样的关系,所以对于dict里面的dict, 我们也只能用valueForKeyPath。这里我们也看到KVC对于array(set), 做了特殊处理,不是简单操作collection上,而是 针对这些collection里面的元素进行操作,同样KVC也提供更多地操作,例如@sum这些针对collection,有兴趣的同学可以去用下。

场景4,当我们执行 NSArray *products = [people valueForKey:@“products”],我们希望的是[people products],可是people没有这样的方法, KVC又会为我们带来些什么呢?

首先会去找getProdcuts or products or isProducts, 按照这样的顺序去查找,第一个找到的就返回.

然后会去找countOfProducts and either objectInProductsAtIndex: or ProductsAtIndexes, 如果找到,就会去找countOfProducts and enumeratorOfProducts and memberOfProducts 这个2个方法都找到了,KVC才会给我们返回一个代理的NSKeyValueArray,用于我们后续的操作(addProduct之类的)。

如果有个变量叫做 products, isProducts, products or isProducts, KVC会直接就使用这样的变量,如果你觉得直接用这样的变量是破坏了封装, 可以禁止这样的行为发生,重载 +accessInstanceVariablesDirectly,返回NO。

简单来说,valueForKey, 会给我们带来一个代理array, 如果我们实现了某些方法,上诉的这些方法只是针对NSArray, 对于mutable的collection, 我们还需要提供其他 方法的实现才行。

KVO给我们提供了更少的代码,和比NSNotification好处,不需要修改被观察的class, 永远都是观察你的人做事情。 但是KVO也有些毛病, 1. 如果没有observer监听key path, removeObsever:forKeyPath:context: 这个key path, 就会crash, 不像NSNotificationCenter removeObserver。 2. 对代码你很难发现谁监听你的property的改动,查找起来比较麻烦。 3. 对于一个复杂和相关性很高的class,最好还是不要用KVO, 就用delegate 或者 notification的方式比较简洁。

Summary
尽量使用KVC可以大大地减少我们的代码量,当遇到property的时候,可以多想想是否可以KVC来帮助我,是否可以用KVC来重构代码, 当需要加入observer模式时,可以考虑下KVO, 在高性能的observer里面,KVO会给我们很好的帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容