KVC簡(jiǎn)介
我們知道可以通過setter惦积、getter方法來設(shè)置和修改對(duì)象的屬性接校,也知道如何通過簡(jiǎn)化的點(diǎn)語法來設(shè)置、修改對(duì)象的屬性狮崩。實(shí)際上蛛勉,Objective-C還支持一種更靈活的操作方式,這種方式允許以字符串形式間接操作對(duì)象的屬性睦柴,這種方式的全稱是Key Value Coding(簡(jiǎn)稱KVC)诽凌,即鍵值編碼。
簡(jiǎn)單的KVC
最基本的KVC由NSKeyValueCoding協(xié)議提供支持坦敌,最基本的操作屬性的兩個(gè)方法如下:
setValue:屬性值forKey:屬性名:為指定屬性設(shè)置值侣诵。
valueForKey:屬性名:獲取指定屬性的值。
用法如下:
@property (nonatomic, copy) NSString *name;
設(shè)定:
[object setValue:@"Suxiaoyao" forKey:@"name"]
取值:
NSString *nameStr = [object valueForKey:@"name"]
對(duì)于setValue:屬性值 forKey:@"name"狱窘;代碼杜顺,底層的執(zhí)行機(jī)制如下:
(1).程序優(yōu)先調(diào)用“setName:屬性值;”代碼通過setter方法完成設(shè)置。
(2).如果該類沒有setName:方法训柴,KVC機(jī)制會(huì)搜索該類名為_name的成員變量哑舒,找到后對(duì)_name成員變量賦值。
(3).如果該類既沒有setName:方法幻馁,也沒有定義_name成員變量洗鸵,KVC機(jī)制會(huì)搜索該類名為name的成員變量越锈,找到后對(duì)name成員變量賦值。
(4).如果上面3條都沒有找到膘滨,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的setValue: forUndefinedKey:方法甘凭。默認(rèn)setValue: forUndefinedKey:方法會(huì)引發(fā)一個(gè)異常,將會(huì)導(dǎo)致程序崩潰火邓。
對(duì)于“valueForKey:@"name";”代碼丹弱,底層執(zhí)行機(jī)制如下:
(1).程序優(yōu)先調(diào)用"name;"代碼來獲取該getter方法的返回值。
(2).如果該類沒有name方法铲咨,KVC機(jī)制會(huì)搜索該類名為_name的成員變量躲胳,找到后返回_name成員變量的值。
(3).如果該類既沒有name方法纤勒,也沒有定義_name成員變量坯苹,KVC機(jī)制會(huì)搜索該類名為name的成員變量,找到后返回name成員變量的值摇天。
(4).如果上面3條都沒有找到粹湃,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的valueForUndefinedKey:方法。默認(rèn)valueForUndefinedKey:方法會(huì)引發(fā)一個(gè)異常泉坐,將會(huì)導(dǎo)致程序崩潰为鳄。
處理不存在的key
前面提到過,當(dāng)使用KVC方式操作屬性時(shí)腕让,這些屬性可能不存在孤钦,此時(shí)系統(tǒng)只是引發(fā)了異常,并沒有進(jìn)行任何特別的處理记某。但是在我們的實(shí)際開發(fā)中司训,結(jié)合我們的業(yè)務(wù)場(chǎng)景,我們總是不希望程序會(huì)崩潰液南,此時(shí)我們可以考慮重寫setValue: forUndefinedKey:方法與valueForUndefinedKey:方法。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {? ?
NSLog(@"您設(shè)置的key:[%@]不存在", key);? ?
NSLog(@"您設(shè)置的value為:[%@]", value);
}
- (id)valueForUndefinedKey:(NSString *)key {?
? NSLog(@"您訪問的key:[%@]不存在", key);
returnnil;
}
這樣的話勾徽,當(dāng)KVC操作并不存在的key時(shí)滑凉,KVC機(jī)制總是會(huì)調(diào)用重寫的方法進(jìn)行處理,通過這種處理機(jī)制喘帚,可以非常方便的定制自己的處理行為畅姊。
處理nil值
當(dāng)調(diào)用KVC來設(shè)置對(duì)象的屬性時(shí),如果屬性的類型是對(duì)象類型(如NSString)吹由,嘗試將屬性設(shè)置為nil若未,是合法的,程序可以正常運(yùn)行割岛。
但是如果屬性的類型是基本類型(如int振愿、float、double)腰池,嘗試將屬性設(shè)置為nil隙疚,程序?qū)?huì)崩潰引發(fā)以下異常'NSInvalidArgumentException',并且從提示信息可以知道setNilValueForKey:方法導(dǎo)致了這個(gè)異常壤追。當(dāng)程序嘗試為某個(gè)屬性設(shè)置nil值時(shí),如果該屬性并不接受nil值供屉,那么程序?qū)?huì)自動(dòng)執(zhí)行該對(duì)象的setNilValueForKey:方法行冰。我們同樣可以重寫這個(gè)方法:
- (void)setNilValueForKey:(NSString *)key {
//對(duì)不能接受nil的屬性進(jìn)行處理
if([key isEqualToString:@"price"]) {
//對(duì)應(yīng)你具體的業(yè)務(wù)來處理price =0;? ?
}else{? ? ?
? [supersetNilValueForKey:key];?
? }
}
我們可以通過重寫這個(gè)方法,并且根據(jù)我們不同的業(yè)務(wù)場(chǎng)景做單獨(dú)處理伶丐。
Key路徑(Key Path)
KVC 同樣允許我們通過關(guān)系來訪問對(duì)象悼做。假設(shè) person 對(duì)象有屬性 address,address 有屬性 city哗魂,我們可以這樣通過 person 來訪問 city:
[person valueForKeyPath:@"address.city"];
值得注意的是這里我們調(diào)用 -valueForKeyPath: 而不是 -valueForKey:贿堰。
KVC協(xié)議中為操作Key路徑的方法如下:
setValue:forKeyPath: 根據(jù)Key路徑設(shè)置屬性值
valueForKeyPath: 根據(jù)Key路徑獲取屬性值
集合的操作
一個(gè)常常被忽視的 KVC 特性是它對(duì)集合操作的支持。舉個(gè)例子啡彬,我們可以這樣來獲得一個(gè)數(shù)組中最大的值:
NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);
或者說羹与,我們有一個(gè) Transaction 對(duì)象的數(shù)組,對(duì)象有屬性 amount 的話庶灿,我們可以這樣獲得最大的 amount:
NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);
當(dāng)我們調(diào)用[a valueForKeyPath:@"@max.amount"]的時(shí)候纵搁,它會(huì)在數(shù)組 a的每個(gè)元素中調(diào)用 -valueForKey:@"amount"然后返回最大的那個(gè)。KVC 的蘋果官方文檔有一個(gè)章節(jié)Collection Operators詳細(xì)的講述了類似的用法往踢。
KVC小結(jié)
前面介紹了這么多內(nèi)容腾誉,大家可能感到疑惑,為什么要用KVC方式來操作呢峻呕?直接調(diào)用對(duì)象的setter與getter方法進(jìn)行操作不可以嗎利职?是不是KVC方式的性能更好呢?實(shí)際上瘦癌,通過KVC操作對(duì)象的性能比通過setter猪贪、getter方式操作的性能更差,使用KVC編程的優(yōu)勢(shì)是更加簡(jiǎn)潔讯私,更適合提煉一些通用性質(zhì)的代碼热押。由于KVC允許通過字符串形式來操作對(duì)象的屬性,這個(gè)字符串既可是常量斤寇,也可是變量桶癣,因此具有極高的靈活性。
鍵值監(jiān)聽(KVO)
在iOS應(yīng)用的開發(fā)過程中娘锁,iOS應(yīng)用通常會(huì)把應(yīng)用程序組件分開成數(shù)據(jù)模型組件和視圖組件牙寞,其中數(shù)據(jù)模型組件負(fù)責(zé)維護(hù)應(yīng)用程序的狀態(tài)數(shù)據(jù),而視圖組件則負(fù)責(zé)顯示數(shù)據(jù)模型組件內(nèi)部的狀態(tài)數(shù)據(jù)莫秆。
對(duì)于上面的設(shè)計(jì)結(jié)構(gòu)间雀,如果程序存在的需求是:在數(shù)據(jù)模型組件的狀態(tài)數(shù)據(jù)發(fā)生改變時(shí)悔详,視圖組件能動(dòng)態(tài)地更新自己,及時(shí)顯示數(shù)據(jù)模型組件更新后的數(shù)據(jù)雷蹂。
iOS為我們提供了一種優(yōu)秀的解決方案:利用KVO(Key Value Observing)機(jī)制伟端。
KVO機(jī)制NSKeyValueObserving協(xié)議提供支持,當(dāng)然匪煌,NSObject遵守了該協(xié)議责蝠,因此,NSObject的子類都可使用該協(xié)議中的方法萎庭,該協(xié)議包含如下常用的方法可用于注冊(cè)監(jiān)聽器:
addObserver:forKeyPath:options:context: 注冊(cè)一個(gè)監(jiān)聽器用于監(jiān)聽指定Key路徑
removeObserver:forKeyPath: 為指定Key路徑刪除指定的監(jiān)聽器
removeObserver:forKeyPath:context: 為指定Key路徑刪除指定的監(jiān)聽器霜医,只是多了一個(gè)context參數(shù)。
對(duì)于上面的需求驳规,很容易想到可以讓視圖組件來監(jiān)聽數(shù)據(jù)模型組件的改變肴敛,當(dāng)數(shù)據(jù)模型組件的key路徑對(duì)應(yīng)的屬性發(fā)生改變時(shí),作為監(jiān)聽器的視圖組件將被激發(fā)吗购,激發(fā)時(shí)就會(huì)回調(diào)監(jiān)聽器自身的監(jiān)聽方法医男,該監(jiān)聽方法如下:
observeValueForKeyPath:ofObject:change:context:
由此可見,作為監(jiān)聽器的視圖組件需要重寫observeValueForKeyPath:ofObject:change:context:方法捻勉,重寫該方法時(shí)就可以得到最新修改的數(shù)據(jù)镀梭,從而使用最新的數(shù)據(jù)來更新視圖組件的顯示。
KVO編程的步驟如下:
為被監(jiān)聽對(duì)象(通常是數(shù)據(jù)模型組件)注冊(cè)監(jiān)聽器
重寫監(jiān)聽器的observeValueForKeyPath:ofObject:change:context:方法
移除監(jiān)聽器
KVO實(shí)例場(chǎng)景
我們要監(jiān)聽一個(gè)人的心跳踱启,并且在屏幕上顯示出來
我們定義出model
@interface Person : NSObject/**
心跳
*/@property (nonatomic, copy) NSString *heartbeat;
@end
@implementation Person
@end
定義此model為Controller的屬性报账,實(shí)例化它,監(jiān)聽它的屬性埠偿,并顯示在當(dāng)前的View里邊
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@property (nonatomic, strong) UILabel *heartbeatLabel;
@end
@implementation ViewController
- (void)viewDidLoad {?
? [superviewDidLoad];
// Do any additional setup after loading the view, typically from a nib.self.person = [[Person alloc] init];?
[self.person setValue:@"72"forKey:@"heartbeat"];? ?
[self.person addObserver:self forKeyPath:@"heartbeat"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];?
self.heartbeatLabel = [[UILabel alloc] initWithFrame:CGRectMake(100,100,100,30)];
self.heartbeatLabel.textColor = [UIColor redColor]; ?
self.heartbeatLabel.text = [self.person valueForKey:@"heartbeat"];? ?
[self.view addSubview:self.heartbeatLabel];? ?
UIButton *runButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];? ?
runButton.frame = CGRectMake(0,0,100,30);? ?
runButton.backgroundColor = [UIColor redColor];? ?
[runButton addTarget:self action:@selector(run:) forControlEvents:UIControlEventTouchUpInside];? ?
[self.view addSubview:runButton];
}
當(dāng)點(diǎn)擊button的時(shí)候透罢,調(diào)用run方法,修改對(duì)象的屬性
- (void)run:(UIButton *)sender {? ?
[self.person setValue:@"100"forKey:@"heartbeat"];
}
實(shí)現(xiàn)回調(diào)方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{
if([keyPath isEqualToString:@"heartbeat"])? ? {? ? ? ?
self.heartbeatLabel.text = [change objectForKey:@"new"];? ?
}
}
增加觀察與取消觀察是成對(duì)出現(xiàn)的冠蒋,所以需要在最后的時(shí)候羽圃,移除觀察者
- (void)dealloc {? ? [self.person removeObserver:self forKeyPath:@"heartbeat"];}
KVO小結(jié)
KVO這種編碼方式使用起來很簡(jiǎn)單,很適用于model修改后浊服,引發(fā)的view的變化這種情況统屈,就像上邊的例子那樣,當(dāng)更改屬性的值后牙躺,監(jiān)聽對(duì)象會(huì)立即得到通知。