天天看點

【iOS】KVC 和 KVO 的使用場景

原文位址:http://www.aichengxu.com/view/73620

【iOS】KVC 和 KVO 的使用場景,有需要的朋友可以參考下。

Key Value Coding

Key Value Coding是cocoa的一個标準組成部分,它能讓我們可以通過name(key)的方式通路property, 不必調用明确的property accssor, 如我們有個property叫做foo, 我們可以foo直接通路它,同樣我們也可以用KVC來完成[Object valueForKey:@“foo”], 有同學就會問了, 這樣做有什麼好處呢?主要的好處就是來減少我們的代碼量。

下面我們來看看幾個例子,就明白了KVO的用法和好處了,假設這樣個類叫做People,

  1. @interfacePeople:NSObject
  2. @property(nonatomic,strong)NSString*name;
  3. @property(nonatomic,strong)NSNumber*age;
  4. @end

場景1,apple 官網的一個例子,當我們需要統計很多People的時候,每一行是一個人的執行個體,并且有2列屬性,name, age, 這時候我們可以會這樣做,

  1. -(id)tableView:(NSTableView*)tableview
  2. objectValueForTableColumn:(id)columnrow:(NSInteger)row{
  3. People*people=[peoleArrayobjectAtIndex:row];
  4. if([[columnidentifier]isEqualToString:@"name"]){
  5. return[peoplename];
  6. }
  7. if([[columnidentifier]isEqualToString:@"age"]){
  8. return[peopleage];
  9. }
  10. //Andsoon.
  11. }

同樣我們也可以用KVC,幫助我們化簡這些if, 因為name, age其實都是property, 我們可以直接通過key來通路,是以整理過後是

  1. People*people=[peopleArrayobjectAtIndex:row];
  2. return[peoplevalueForKey:[columnidentifier]];

場景2,這下我們有了server, server的某個api(listPeople??), 會傳回我們json格式一個數組,裡面包含這樣dict{name:xx, age:xx}這樣的資料, 我們希望用這些dict資料構造出我們的people來,通常我們的做法是,為我們People類寫一個static factory方法專門用來處理dict來, 把dict裡面的資料取出來, 然後建立個空的People對象,然後依次設定property。然而當這樣類似People的與server互動的類多了,我們就要為每個類都要加上這樣的wrapper, 

是否有種簡單辦法來設定這樣的屬性,當然就是我們的KVC了。

  1. -(id)initWithDictionary:(NSMutableDictionary*)jsonObject
  2. {
  3. if((self=[superinit]))
  4. {
  5. [selfinit];
  6. [selfsetValuesForKeysWithDictionary:jsonObject];
  7. }
  8. returnself;
  9. }

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就會 抛出異常。

  1. -(void)setValue:(id)valueforUndefinedKey:(NSString*)key
  2. {
  3. if([keyisEqualToString:@"nameXXX"])
  4. self.name=value;
  5. if([keyisEqualToString:@"ageXXX"])
  6. self.age=value;
  7. else
  8. [supersetValue:valueforKey:key];
  9. }

是以隻要重載這個方法,就可以處理了那些無法跟property相比對的key了,預設的實作是抛出一個NSUndefinedKeyException,又有同學發問了如果 這時候server傳回的People有了内嵌的json(如Products{product1{count:xx, sumPrice:xx}}, product2{} ….),又該怎麼辦,能把這個内嵌的json轉化成我們的用戶端的Product類嘛, 當然可以這時候就需要重載setValue:forKey, 單獨處理”Products”這個key, 

把它wrapper成我們需要的class

  1. -(void)setValue:(id)valueforKey:(NSString*)key
  2. {
  3. if([keyisEqualToString:@"products"])
  4. {
  5. for(NSMutableDictionary*productDictinvalue)
  6. {
  7. Prodcut*product=[[Productalloc]initWithDictionary:prodcutDict];
  8. [self.productsaddObject:product];
  9. }
  10. }
  11. }

場景3,我們需要把一個數組裡的People的名字的首字母大寫,并且把新的名字存入新的數組, 這時候通常做法會是周遊整個數組,然後把每個People的name取出來,調用 capitalizedString 然後把新的String加入新的數組中。 有了KVC就有了新做法:

  1. [arrayvalueForKeyPath:@"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, 我們還需要提供其他 方法的實作才行。

Key Value Observing

Key Value Observing, 顧名思義就是一種observer 模式用于監聽property的變化,KVO跟NSNotification有很多相似的地方, 用addObserver:forKeyPath:options:context:去start observer, 用removeObserver:forKeyPath:context去stop observer, 回調就是observeValueForKeyPath:ofObject:change:context:。

  1. -(void)removeObservation{
  2. [self.objectremoveObserver:self
  3. forKeyPath:self.property];
  4. }
  5. -(void)addObservation{
  6. [self.objectaddObserver:selfforKeyPath:self.property
  7. options:0
  8. context:(__bridgevoid*)self];
  9. }
  10. -(void)observeValueForKeyPath:(NSString*)keyPath
  11. ofObject:(id)object
  12. change:(NSDictionary*)change
  13. context:(void*)context{
  14. if((__bridgeid)context==self){
  15. //隻處理跟我們目前class的property更新
  16. }
  17. else{
  18. [superobserveValueForKeyPath:keyPathofObject:object
  19. change:changecontext:context];
  20. }
  21. }

對于KVO來說,我們要做的隻是簡單update 我們的property資料,不需要像NSNotificationCenter那樣關心是否有人在監聽你的請求,如果沒有人監聽該怎麼辦, 所有addObserver, removeObserver, callback 都是想要監聽的你的property的class做的事情。 曾經做個項目,用NSNotificationCenter post Notification在一個network callback裡面,可是這時候因為最早的addObserver的class被釋放了, 

接着生成的addObserver的class, 就接受到了上一個observer該監聽的事件,是以造成了錯誤,那時候的解決方案是為addObserve key做unique,不會2次addObserver 的key是相同的,但是有了KVO, 我們同樣可以用KVO來完成,當addOberver的的object remove的時候,就不會有這樣的callback被調用了。

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會給我們很好的幫助。