http://www.cocoachina.com/industry/20140224/7866.html
Key Value Coding
Key Value Coding是cocoa的一個(gè)標(biāo)準(zhǔn)組成部分,它能讓我們可以通過name(key)的方式訪問property, 不必調(diào)用明確的property accssor, 如我們有個(gè)property叫做foo, 我們可以foo直接訪問它,同樣我們也可以用KVC來完成[Object valueForKey:@“foo”], 有同學(xué)就會(huì)問了, 這樣做有什么好處呢?主要的好處就是來減少我們的代碼量。
下面我們來看看幾個(gè)例子,就明白了KVO的用法和好處了,假設(shè)這樣個(gè)類叫做People,
@interface People: NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@end
場景1,apple 官網(wǎng)的一個(gè)例子,當(dāng)我們需要統(tǒng)計(jì)很多People的時(shí)候,每一行是一個(gè)人的實(shí)例,并且有2列屬性,name, age, 這時(shí)候我們可以會(huì)這樣做,
-
(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, 因?yàn)閚ame, age其實(shí)都是property, 我們可以直接通過key來訪問,所以整理過后是
People *people = [peopleArray objectAtIndex:row];
return [people valueForKey:[column identifier]];
場景2,這下我們有了server, server的某個(gè)api(listPeople??), 會(huì)返回我們json格式一個(gè)數(shù)組,里面包含這樣dict{name:xx, age:xx}這樣的數(shù)據(jù), 我們希望用這些dict數(shù)據(jù)構(gòu)造出我們的people來,通常我們的做法是,為我們People類寫一個(gè)static factory方法專門用來處理dict來, 把dict里面的數(shù)據(jù)取出來, 然后創(chuàng)建個(gè)空的People對象,然后依次設(shè)置property。然而當(dāng)這樣類似People的與server交互的類多了,我們就要為每個(gè)類都要加上這樣的wrapper, 是否有種簡單辦法來設(shè)置這樣的屬性,當(dāng)然就是我們的KVC了。
-(id) initWithDictionary:(NSMutableDictionary*) jsonObject
{
if((self = [super init]))
{
[self init];
[self setValuesForKeysWithDictionary:jsonObject];
}
return self;
}
setValuesForKeysWithDictionary, 會(huì)為我們把和dictionary的key名字相同的class proerty設(shè)置上dict中key對應(yīng)的value, 是不是很方便呀,但是有同學(xué)又要問了 如果json里面的某些key就是和object的property名字不一樣呢,或者有些server返回的字段是objc保留字如”id”, “description”等, 我們也希望也map dict to object, 這時(shí)候我們就需要用上setValue:forUndefinedKey, 因?yàn)槿绻覀儾惶幚磉@些Undefined Key,還是用setValuesForKeysWithDictionary就會(huì) 拋出異常。
- (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];
}
所以只要重載這個(gè)方法,就可以處理了那些無法跟property相匹配的key了,默認(rèn)的實(shí)現(xiàn)是拋出一個(gè)NSUndefinedKeyException,又有同學(xué)發(fā)問了如果 這時(shí)候server返回的People有了內(nèi)嵌的json(如Products{product1{count:xx, sumPrice:xx}}, product2{} ….),又該怎么辦,能把這個(gè)內(nèi)嵌的json轉(zhuǎn)化成我們的客戶端的Product類嘛, 當(dāng)然可以這時(shí)候就需要重載setValue:forKey, 單獨(dú)處理”Products”這個(gè)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,我們需要把一個(gè)數(shù)組里的People的名字的首字母大寫,并且把新的名字存入新的數(shù)組, 這時(shí)候通常做法會(huì)是遍歷整個(gè)數(shù)組,然后把每個(gè)People的name取出來,調(diào)用 capitalizedString 然后把新的String加入新的數(shù)組中。 有了KVC就有了新做法:
[array valueForKeyPath:@"name.capitalizedString"]
我們看到valueForKeyPath, 為什么用valueForKeyPath, 不用valueForKey, 因?yàn)関alueForKeyPath可以傳遞關(guān)系,例如這里是每個(gè)People的name property的String的capitalizedString property, 而valueForKey不能傳遞這樣的關(guān)系,所以對于dict里面的dict, 我們也只能用valueForKeyPath。這里我們也看到KVC對于array(set), 做了特殊處理,不是簡單操作collection上,而是 針對這些collection里面的元素進(jìn)行操作,同樣KVC也提供更多地操作,例如@sum這些針對collection,有興趣的同學(xué)可以去用下。
場景4,當(dāng)我們執(zhí)行NSArray *products = [people valueForKey:@“products”],我們希望的是[people products],可是people沒有這樣的方法, KVC又會(huì)為我們帶來些什么呢?
首先會(huì)去找getProdcuts or products or isProducts, 按照這樣的順序去查找,第一個(gè)找到的就返回
然后會(huì)去找countOfProducts and either objectInProductsAtIndex: or ProductsAtIndexes, 如果找到,就會(huì)去找countOfProducts and enumeratorOfProducts and memberOfProducts 這個(gè)2個(gè)方法都找到了,KVC才會(huì)給我們返回一個(gè)代理的NSKeyValueArray,用于我們后續(xù)的操作(addProduct之類的)。
如果有個(gè)變量叫做 products, isProducts, products or isProducts, KVC會(huì)直接就使用這樣的變量,如果你覺得直接用這樣的變量是破壞了封裝, 可以禁止這樣的行為發(fā)生,重載 +accessInstanceVariablesDirectly,返回NO。
簡單來說,valueForKey, 會(huì)給我們帶來一個(gè)代理array, 如果我們實(shí)現(xiàn)了某些方法,上訴的這些方法只是針對NSArray, 對于mutable的collection, 我們還需要提供其他 方法的實(shí)現(xiàn)才行。
Key Value Observing
Key Value Observing, 顧名思義就是一種observer 模式用于監(jiān)聽property的變化,KVO跟NSNotification有很多相似的地方, 用addObserver:forKeyPath:options:context:去start observer, 用removeObserver:forKeyPath:context去stop observer, 回調(diào)就是observeValueForKeyPath:ofObject:change:context:。
(void)removeObservation {
[self.object removeObserver:self
forKeyPath:self.property];
}(void)addObservation {
[self.object addObserver:self forKeyPath:self.property
options:0
context:(__bridge void*)self];
}(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ((__bridge id)context == self) {
// 只處理跟我們當(dāng)前class的property更新
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
對于KVO來說,我們要做的只是簡單update 我們的property數(shù)據(jù),不需要像NSNotificationCenter那樣關(guān)心是否有人在監(jiān)聽你的請求,如果沒有人監(jiān)聽該怎么辦, 所有addObserver, removeObserver, callback 都是想要監(jiān)聽的你的property的class做的事情。 曾經(jīng)做個(gè)項(xiàng)目,用NSNotificationCenter post Notification在一個(gè)network callback里面,可是這時(shí)候因?yàn)樽钤绲腶ddObserver的class被釋放了, 接著生成的addObserver的class, 就接受到了上一個(gè)observer該監(jiān)聽的事件,所以造成了錯(cuò)誤,那時(shí)候的解決方案是為addObserve key做unique,不會(huì)2次addObserver 的key是相同的,但是有了KVO, 我們同樣可以用KVO來完成,當(dāng)addOberver的的object remove的時(shí)候,就不會(huì)有這樣的callback被調(diào)用了。
KVO給我們提供了更少的代碼,和比NSNotification好處,不需要修改被觀察的class, 永遠(yuǎn)都是觀察你的人做事情。 但是KVO也有些毛病, 1. 如果沒有observer監(jiān)聽key path, removeObsever:forKeyPath:context: 這個(gè)key path, 就會(huì)crash, 不像NSNotificationCenter removeObserver。 2. 對代碼你很難發(fā)現(xiàn)誰監(jiān)聽你的property的改動(dòng),查找起來比較麻煩。 3. 對于一個(gè)復(fù)雜和相關(guān)性很高的class,最好還是不要用KVO, 就用delegate 或者 notification的方式比較簡潔。
Summary
盡量使用KVC可以大大地減少我們的代碼量,當(dāng)遇到property的時(shí)候,可以多想想是否可以KVC來幫助我,是否可以用KVC來重構(gòu)代碼, 當(dāng)需要加入observer模式時(shí),可以考慮下KVO, 在高性能的observer里面,KVO會(huì)給我們很好的幫助。