KVO監(jiān)聽屬性改變
Key-Value Observing
(簡寫為KVO
):它的作用就是用來監(jiān)聽類中屬性值的變化,實現原理其實就是是觀察者模式。被觀察者的屬性發(fā)生改變時,會通知觀察者。舉個例子當指定對象的屬性被修改了,KVO
會自動的去通知相應的觀察者。
KVO
的優(yōu)點:當有屬性發(fā)生改變時,KVO
會提供自動的消息通知,這樣的架構有很多好處。
首先,開發(fā)人員不需要自己去實現這樣的方案:每次屬性改變了就發(fā)送消息通知,這是KVO
機制提供的最大的優(yōu)點。因為這個方案已經被明確定義,獲得框架級支持,可以方便地采用。開發(fā)人員不需要添加任何代碼,不需要設計自己的觀察者模型,直接可以在工程里使用。
KVO
主要用于用戶界面交互,當多個View
共同使用了同一個實體,當這個實體中的某個屬性改變時,如果需要更新多個界面,KVO
的作用就發(fā)揮出來了。
添加觀察者
//通過此方法即可添加對象的觀察者
/*
Observer:觀察者
KeyPath:觀察webView哪個屬性
options:NSKeyValueObservingOptionNew:觀察新值改變
*/
- (void)addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context;
例如 監(jiān)聽UIWebView
的canGoBack
屬性的變化:
[webView addObserver:self forKeyPath:@"canGoBack" options:NSKeyValueObservingOptionNew context:nil];
調用代理方法 只要被觀察對象屬性有新值就會調用該代理方法
/**
當屬性值發(fā)生變化的時候,這個方法會被回調
@param keyPath 鍵值路徑
@param object 監(jiān)聽對象
@param change 變化的值
@param context 傳遞的內容
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
上述代理方法中我們看到有一個特殊的參數:第三個參數:NSDirctionary
類型的。我們會發(fā)現他有兩個鍵值對:key是:new和old。他們就是分別代表這個屬性值變化的前后值,同時他們的
Value也和之前我們添加監(jiān)聽對象時設置的第三個參數有關:
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld。那個地方設置了幾種狀態(tài),這里的
NSDirctionary`中就會有幾個鍵值對
調用dealloc方法,移除觀察者
- (void)dealloc{
//移除觀察者
[self.webView removeObserver:self forKeyPath:@"canGoBack"];
}
備注
KVO只能檢測類中的屬性,并且屬性名都是通過NSString來查找,編譯器不會補全,容易寫錯
KVC
KVC
是一種使用字符串標識符,間接訪問對象屬性的機制,它是很多技術的基礎。主要的方法就兩對方法:
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
其實上面的幾個方法使用起來非常的相似,只不過forKeyPath
鍵值路徑的功能更加強大一些.用來訪問屬性中的屬性
,更加推薦使用forKeyPath
鍵值路徑.
既然這里說到了KVC
,我就想聊聊前一陣子在使用setValuesForKeysWithDictionary
遇到的一個問題
setValue:forUndefinedKey
當然我們在做字典轉模型的時候一般都是使用MJExtension
來幫助我們完成,其實MJExtension
內部也是運用了Runtime
運行時機制和setValuesForKeysWithDictionary
假設我們這里有一個Person
類,里面只有少的可憐的兩個屬性:
@property (nonatomic,copy)NSString *name;//姓名
@property (nonatomic,copy)NSString *age;//年齡
然后下面我們模擬一個給Person
賦值的小案例:
NSDictionary *dataSource = @{@"name":@"張三",
@"age":@"18"
};
Person *per=[[Person alloc]init];
// per.name=dataSource[@"name"];
// per.age=dataSource[@"age"];
[per setValuesForKeysWithDictionary:dataSource];
NSLog(@"Person.name=%@",per.name);
NSLog(@"Person.age=%@",per.age);
如果Person
中的屬性特別多的時候,一個一個的賦值比較麻煩,所以我們一般都是通過setValuesForKeysWithDictionary
方法來幫助我們實現字典轉模型.上面的代碼一點問題都沒有.但是如果后臺給我們的數據多了一個字段的時候,比如:
NSDictionary *dataSource = @{@"name":@"張三",
@"age":@"18",
@"height":@"1.88"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
系統編譯可以通過,但是運行時后崩潰了,下面是崩潰日志:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x60800002d6e0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key height.'
這是因為我們的對象中沒有height
這個屬性,這時候姐姐我教你一個方法:
#import "Person.h"
@implementation Person
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
@end
我們只需要在對象中實現setValue:forUndefinedKey
這個方法即可,這樣就會直接過濾掉給不存在的鍵值賦值的問題,是不是很簡單??
還有一種情況,估計大家也應該經常遇到:
NSDictionary *dataSource = @{@"name":@"張三",
@"age":@"18",
@"height":@"1.88",
@"id":@"101"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
NSLog(@"Person.ID=%@",per.ID);
因為后臺在數據庫中的主鍵經常起名id
,這其實沒有任何問題,但是后臺人員在給我們返回字段的時候沒有經過任何處理就把該字段扔給我們了,要知道的是id
可是蘋果的關鍵字,是不能夠當做屬性來使用的.
#import "Person.h"
@implementation Person
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if([key isEqualToString: @"id"])
{
self.ID = value;
}
}
@end
setValue:forUndefinedKey:
我們可以使用該方法過濾掉不存在的鍵值對而防止崩潰,同時在該方法中我們還可以改變因系統的關鍵字引起的問題等