(轉)【iOS】KVC 和 KVO 的使用場景

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,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,這下我們有了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, 我們還需要提供其他 方法的實現才行。

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:。

  • (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) {
    // 只處理跟我們當前class的property更新
    }
    else {
    [super observeValueForKeyPath:keyPath ofObject:object
    change:change context:context];
    }
    }

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 本文摘自:http://www.cocoachina.com/industry/20140224/7866.htm...
    0o凍僵的企鵝o0閱讀 345評論 0 1
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實翻譯一下就很簡單了,就是指iO...
    朽木自雕也閱讀 1,571評論 6 1
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,751評論 0 9
  • 在編程中,最常見的就是程序的流程取決于你所使用的各種變量和屬性的值,根據變量和屬性的值確定后面運行的代碼,有時會檢...
    pro648閱讀 1,654評論 2 27
  • 在辭舊迎新的日子里,你依然讓我想起。 雖然你的照片已經被我撕碎,飄散在風里,我依然懷念你。 認識你是我的劫數,或許...
    千瓊皎皎閱讀 268評論 0 1