KVO監(jiān)聽屬性改變
Key-Value Observing
(簡寫為KVO
):它的作用就是用來監(jiān)聽類中屬性值的變化,實現(xiàn)原理其實就是是觀察者模式妓美。被觀察者的屬性發(fā)生改變時僵腺,會通知觀察者。舉個例子當(dāng)指定對象的屬性被修改了部脚,KVO
會自動的去通知相應(yīng)的觀察者想邦。
KVO
的優(yōu)點:當(dāng)有屬性發(fā)生改變時,KVO
會提供自動的消息通知委刘,這樣的架構(gòu)有很多好處丧没。
首先,開發(fā)人員不需要自己去實現(xiàn)這樣的方案:每次屬性改變了就發(fā)送消息通知锡移,這是KVO
機制提供的最大的優(yōu)點呕童。因為這個方案已經(jīng)被明確定義,獲得框架級支持淆珊,可以方便地采用夺饲。開發(fā)人員不需要添加任何代碼,不需要設(shè)計自己的觀察者模型施符,直接可以在工程里使用往声。
KVO
主要用于用戶界面交互,當(dāng)多個View
共同使用了同一個實體戳吝,當(dāng)這個實體中的某個屬性改變時浩销,如果需要更新多個界面,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];
調(diào)用代理方法 只要被觀察對象屬性有新值就會調(diào)用該代理方法
/**
當(dāng)屬性值發(fā)生變化的時候慢洋,這個方法會被回調(diào)
@param keyPath 鍵值路徑
@param object 監(jiān)聽對象
@param change 變化的值
@param context 傳遞的內(nèi)容
*/
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context;
上述代理方法中我們看到有一個特殊的參數(shù):第三個參數(shù):NSDirctionary
類型的塘雳。我們會發(fā)現(xiàn)他有兩個鍵值對:key是:new和old。他們就是分別代表這個屬性值變化的前后值普筹,同時他們的
Value也和之前我們添加監(jiān)聽對象時設(shè)置的第三個參數(shù)有關(guān):
NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld败明。那個地方設(shè)置了幾種狀態(tài),這里的
NSDirctionary`中就會有幾個鍵值對
調(diào)用dealloc方法太防,移除觀察者
- (void)dealloc{
//移除觀察者
[self.webView removeObserver:self forKeyPath:@"canGoBack"];
}
備注
KVO只能檢測類中的屬性妻顶,并且屬性名都是通過NSString來查找,編譯器不會補全杏头,容易寫錯
KVC
KVC
是一種使用字符串標(biāo)識符盈包,間接訪問對象屬性的機制沸呐,它是很多技術(shù)的基礎(chǔ)醇王。主要的方法就兩對方法:
- (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
當(dāng)然我們在做字典轉(zhuǎn)模型的時候一般都是使用MJExtension
來幫助我們完成,其實MJExtension
內(nèi)部也是運用了Runtime
運行時機制和setValuesForKeysWithDictionary
假設(shè)我們這里有一個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
方法來幫助我們實現(xiàn)字典轉(zhuǎn)模型.上面的代碼一點問題都沒有.但是如果后臺給我們的數(shù)據(jù)多了一個字段的時候,比如:
NSDictionary *dataSource = @{@"name":@"張三",
@"age":@"18",
@"height":@"1.88"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
系統(tǒng)編譯可以通過,但是運行時后崩潰了,下面是崩潰日志:
*** 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
我們只需要在對象中實現(xiàn)setValue:forUndefinedKey
這個方法即可,這樣就會直接過濾掉給不存在的鍵值賦值的問題,是不是很簡單??
還有一種情況,估計大家也應(yīng)該經(jīng)常遇到:
NSDictionary *dataSource = @{@"name":@"張三",
@"age":@"18",
@"height":@"1.88",
@"id":@"101"
};
Person *per=[[Person alloc]init];
[per setValuesForKeysWithDictionary:dataSource];
NSLog(@"Person.ID=%@",per.ID);
因為后臺在數(shù)據(jù)庫中的主鍵經(jīng)常起名id
,這其實沒有任何問題,但是后臺人員在給我們返回字段的時候沒有經(jīng)過任何處理就把該字段扔給我們了,要知道的是id
可是蘋果的關(guān)鍵字,是不能夠當(dāng)做屬性來使用的.
#import "Person.h"
@implementation Person
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if([key isEqualToString: @"id"])
{
self.ID = value;
}
}
@end
setValue:forUndefinedKey:
我們可以使用該方法過濾掉不存在的鍵值對而防止崩潰,同時在該方法中我們還可以改變因系統(tǒng)的關(guān)鍵字引起的問題等