咱們知道能夠經過setter、getter方法來設置和修改對象的屬性,也知道如何經過簡化的點語法來設置、修改對象的屬性。實際上,Objective-C還支持一種更靈活的操做方式,這種方式容許以字符串形式間接操做對象的屬性,這種方式的全稱是Key Value Coding(簡稱KVC),即鍵值編碼。javascript
最基本的KVC由NSKeyValueCoding協議提供支持,最基本的操做屬性的兩個方法以下:java
用法以下:@property (nonatomic, copy) NSString *name;
設定:[object setValue:@"Suxiaoyao" forKey:@"name"]
取值:NSString *nameStr = [object valueForKey:@"name"]
編程
對於setValue:屬性值 forKey:@"name";代碼,底層的執行機制以下:數組
setValue: forUndefinedKey:
方法。默認setValue: forUndefinedKey:
方法會引起一個異常,將會致使程序崩潰。對於「valueForKey:@"name";」代碼,底層執行機制以下:ide
valueForUndefinedKey:
方法。默認valueForUndefinedKey:
方法會引起一個異常,將會致使程序崩潰。前面提到過,當使用KVC方式操做屬性時,這些屬性可能不存在,此時系統只是引起了異常,並無進行任何特別的處理。可是在咱們的實際開發中,結合咱們的業務場景,咱們老是不但願程序會崩潰,此時咱們能夠考慮重寫setValue: forUndefinedKey:
方法與valueForUndefinedKey:
方法。性能
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"您設置的key:[%@]不存在", key);
NSLog(@"您設置的value爲:[%@]", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"您訪問的key:[%@]不存在", key);
return nil;
}複製代碼
這樣的話,當KVC操做並不存在的key時,KVC機制老是會調用重寫的方法進行處理,經過這種處理機制,能夠很是方便的定製本身的處理行爲。編碼
當調用KVC來設置對象的屬性時,若是屬性的類型是對象類型(如NSString),嘗試將屬性設置爲nil,是合法的,程序能夠正常運行。atom
可是若是屬性的類型是基本類型(如int、float、double),嘗試將屬性設置爲nil,程序將會崩潰引起如下異常'NSInvalidArgumentException',而且從提示信息能夠知道setNilValueForKey:方法致使了這個異常。當程序嘗試爲某個屬性設置nil值時,若是該屬性並不接受nil值,那麼程序將會自動執行該對象的setNilValueForKey:方法。咱們一樣能夠重寫這個方法:spa
- (void)setNilValueForKey:(NSString *)key {
//對不能接受nil的屬性進行處理
if ([key isEqualToString:@"price"]) {
//對應你具體的業務來處理
price = 0;
}else {
[super setNilValueForKey:key];
}
}複製代碼
咱們能夠經過重寫這個方法,而且根據咱們不一樣的業務場景作單獨處理。設計
KVC 一樣容許咱們經過關係來訪問對象。假設 person 對象有屬性 address,address 有屬性 city,咱們能夠這樣經過 person 來訪問 city:
[person valueForKeyPath:@"address.city"];複製代碼
值得注意的是這裏咱們調用 -valueForKeyPath: 而不是 -valueForKey:。
KVC協議中爲操做Key路徑的方法以下:
一個經常被忽視的 KVC 特性是它對集合操做的支持。舉個例子,咱們能夠這樣來得到一個數組中最大的值:
NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);複製代碼
或者說,咱們有一個 Transaction 對象的數組,對象有屬性 amount 的話,咱們能夠這樣得到最大的 amount:
NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);複製代碼
當咱們調用[a valueForKeyPath:@"@max.amount"]
的時候,它會在數組 a的每一個元素中調用 -valueForKey:@"amount"而後返回最大的那個。KVC 的蘋果官方文檔有一個章節 Collection Operators 詳細的講述了相似的用法。
前面介紹了這麼多內容,你們可能感到疑惑,爲何要用KVC方式來操做呢?直接調用對象的setter與getter方法進行操做不能夠嗎?是否是KVC方式的性能更好呢?實際上,經過KVC操做對象的性能比經過setter、getter方式操做的性能更差,使用KVC編程的優點是更加簡潔,更適合提煉一些通用性質的代碼。因爲KVC容許經過字符串形式來操做對象的屬性,這個字符串既但是常量,也但是變量,所以具備極高的靈活性。
在iOS應用的開發過程當中,iOS應用一般會把應用程序組件分開成數據模型組件和視圖組件,其中數據模型組件負責維護應用程序的狀態數據,而視圖組件則負責顯示數據模型組件內部的狀態數據。
對於上面的設計結構,若是程序存在的需求是:在數據模型組件的狀態數據發生改變時,視圖組件能動態地更新本身,及時顯示數據模型組件更新後的數據。
iOS爲咱們提供了一種優秀的解決方案:利用KVO(Key Value Observing)機制。
KVO機制NSKeyValueObserving協議提供支持,固然,NSObject遵照了該協議,所以,NSObject的子類均可使用該協議中的方法,該協議包含以下經常使用的方法可用於註冊監聽器:
對於上面的需求,很容易想到可讓視圖組件來監聽數據模型組件的改變,當數據模型組件的key路徑對應的屬性發生改變時,做爲監聽器的視圖組件將被激發,激發時就會回調監聽器自身的監聽方法,該監聽方法以下:
observeValueForKeyPath:ofObject:change:context:
因而可知,做爲監聽器的視圖組件須要重寫observeValueForKeyPath:ofObject:change:context:方法,重寫該方法時就能夠獲得最新修改的數據,從而使用最新的數據來更新視圖組件的顯示。
KVO編程的步驟以下:
咱們要監聽一我的的心跳,而且在屏幕上顯示出來
咱們定義出model
@interface Person : NSObject
/** 心跳 */
@property (nonatomic, copy) NSString *heartbeat;
@end
@implementation Person
@end複製代碼
定義此model爲Controller的屬性,實例化它,監聽它的屬性,並顯示在當前的View裏邊
@interface ViewController ()
@property (nonatomic, strong) Person *person;
@property (nonatomic, strong) UILabel *heartbeatLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 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];
}複製代碼
當點擊button的時候,調用run方法,修改對象的屬性
- (void)run:(UIButton *)sender {
[self.person setValue:@"100" forKey:@"heartbeat"];
}複製代碼
實現回調方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"heartbeat"])
{
self.heartbeatLabel.text = [change objectForKey:@"new"];
}
}複製代碼
增長觀察與取消觀察是成對出現的,因此須要在最後的時候,移除觀察者
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"heartbeat"];
}複製代碼
KVO這種編碼方式使用起來很簡單,很適用於model修改後,引起的view的變化這種狀況,就像上邊的例子那樣,當更改屬性的值後,監聽對象會當即獲得通知。
但願對您有幫助,若是文章中有問題,歡迎評論留言~,謝謝支持~歡迎關注,我會在空餘時間更新技術文章~