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. @interface People: NSObject 
  2.   
  3. @property (nonatomic, strong) NSString *name; 
  4. @property (nonatomic, strong) NSNumber *age; 
  5.   
  6. @end 
場景1,apple 官網的一個例子,當我們需要統計很多People的時候,每一行是一個人的實例,並且有2列屬性,name, age, 這時候我們可以會這樣做,
  1. - (id)tableView:(NSTableView *)tableview 
  2.       objectValueForTableColumn:(id)column row:(NSInteger)row { 
  3.   
  4.     People *people = [peoleArray objectAtIndex:row]; 
  5.     if ([[column identifier] isEqualToString:@"name"]) { 
  6.         return [people name]; 
  7.     } 
  8.     if ([[column identifier] isEqualToString:@"age"]) { 
  9.         return [people age]; 
  10.     } 
  11.     // And so on. 
同樣我們也可以用KVC,幫助我們化簡這些if, 因爲name, age其實都是property, 我們可以直接通過key來訪問,所以整理過後是
  1. People *people = [peopleArray objectAtIndex:row]; 
  2. return [people valueForKey:[column identifier]]; 
場景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.     if((self = [super init])) 
  3.     { 
  4.         [self init]; 
  5.         [self setValuesForKeysWithDictionary:jsonObject]; 
  6.     } 
  7.     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就會 拋出異常。
  1. - (void)setValue:(id)value forUndefinedKey:(NSString *)key 
  2.     if([key isEqualToString:@"nameXXX"]) 
  3.         self.name = value; 
  4.     if([key isEqualToString:@"ageXXX"]) 
  5.         self.age = value; 
  6.     else 
  7.         [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
  1. -(void) setValue:(id)value forKey:(NSString *)key 
  2.   if([key isEqualToString:@"products"]) 
  3.   { 
  4.     for(NSMutableDictionary *productDict in value) 
  5.     { 
  6.       Prodcut *product = [[Product alloc] initWithDictionary:prodcutDict]; 
  7.       [self.products addObject:product]; 
  8.     } 
  9.   } 
場景3,我們需要把一個數組裏的People的名字的首字母大寫,並且把新的名字存入新的數組, 這時候通常做法會是遍歷整個數組,然後把每個People的name取出來,調用 capitalizedString 然後把新的String加入新的數組中。 有了KVC就有了新做法:
 
  1. [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, 我們還需要提供其他 方法的實現才行。
 
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.object removeObserver:self 
  3.                      forKeyPath:self.property]; 
  4.   
  5. - (void)addObservation { 
  6.     [self.object addObserver:self forKeyPath:self.property 
  7.                      options:0 
  8.                      context:(__bridge void*)self]; 
  9.   
  10. - (void)observeValueForKeyPath:(NSString *)keyPath 
  11.                       ofObject:(id)object 
  12.                         change:(NSDictionary *)change 
  13.                       context:(void *)context { 
  14.   if ((__bridge id)context == self) { 
  15.     // 只處理跟我們當前class的property更新 
  16.   } 
  17.   else { 
  18.     [super observeValueForKeyPath:keyPath ofObject:object 
  19.                            change:change context:context]; 
  20.   } 
 
對於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會給我們很好的幫助。
發佈了3 篇原創文章 · 獲贊 1 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章