一、KVO
KVO的是KeyValue Observe的縮寫,中文是鍵值觀察。這是一個(gè)典型的觀察者模式,利用它可以很容易使用實(shí)現(xiàn)視圖組件和數(shù)據(jù)模型的分離,當(dāng)數(shù)據(jù)模型的屬性值改變之后作為監(jiān)聽器的視圖組件就會(huì)被激發(fā),激發(fā)時(shí)就會(huì)回調(diào)監(jiān)聽器自身,在Objc中實(shí)現(xiàn)KVO則必須實(shí)現(xiàn)NSKeyValueObServing協(xié)議,不過幸運(yùn)的是NSObject已經(jīng)實(shí)現(xiàn)了該協(xié)議,因此幾乎所喲的NSObjectd對(duì)象都可以使用KVO。iOS中有個(gè)Notification的機(jī)制,也可以獲得通知,但這個(gè)機(jī)制需要有個(gè)Center,相比之下KVO更加簡潔而直接。
KVO的使用也很簡單,就是簡單的3步。
? ? ? 1.注冊(cè)需要觀察的對(duì)象的屬性addObserver:forKeyPath:options:context:
? ? ? 2.實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,這個(gè)方法當(dāng)觀察的屬性變化時(shí)會(huì)自動(dòng)調(diào)用
? ? ? 3.取消注冊(cè)觀察removeObserver:forKeyPath:context:
這里有兩種方式,一種是匹配keyPath,另一種是使用context
Demo:事列
A.注冊(cè)需要觀察的對(duì)象
- (void)viewDidLoad
{
? ? [super viewDidLoad];
? ? // 注冊(cè)監(jiān)聽
? ? [self.moveView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
? ? self.p.name = @"小明";
? ? [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:PersonAgeContext];
}
B.實(shí)現(xiàn)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
? ? // 方式1.匹配keypath
? ? if ([keyPath isEqualToString:@"frame"]) {
? ? ? ? NSLog(@"self.moveView.y = %f", self.moveView.y);
? ? }
? ? // 方式2.上下文
? ? if (context == PersonAgeContext) {
? ? ? ? NSLog(@"%@%d歲了", self.p.name, self.p.age);
? ? }
}
C.移除監(jiān)聽
-(void)dealloc // ARC模式下
{
? ? [self.moveView removeObserver:self forKeyPath:@"frame"];
? ? [self.p removeObserver:self forKeyPath:@"age" context:PersonAgeContext];
}
KVO的 優(yōu)勢(shì) :
? ? ? ? 1.能夠提供一種簡單的方法實(shí)現(xiàn)兩個(gè)對(duì)象間的同步。例如:model和view之間同步;
? ? ? ? 2.能夠?qū)Ψ俏覀儎?chuàng)建的對(duì)象,即內(nèi)部對(duì)象的狀態(tài)改變作出響應(yīng),而且不需要改變內(nèi)部對(duì)象(SKD對(duì)象)的實(shí)現(xiàn);
? ? ? ? 3.能夠提供觀察的屬性的最新值以及先前值;
? ? ? ? 4.用key paths來觀察屬性,因此也可以觀察嵌套對(duì)象;
? ? ? ? 5.完成了對(duì)觀察對(duì)象的抽象,因?yàn)椴恍枰~外的代碼來允許觀察值能夠被觀察
? ? ? 缺點(diǎn) :
? ? ? ? 1.我們觀察的屬性必須使用strings來定義。因此在編譯器不會(huì)出現(xiàn)警告以及檢查;
? ? ? ? 2.對(duì)屬性重構(gòu)將導(dǎo)致我們的觀察代碼不再可用;
? ? ? ? 3.復(fù)雜的“IF”語句要求對(duì)象正在觀察多個(gè)值。這是因?yàn)樗械挠^察代碼通過一個(gè)方法來指向;
? ? ? ? 4.當(dāng)釋放觀察者時(shí)不需要移除觀察者。
二、KVC
KVC的常用方法:
- (id)valueForKey:(NSString *)key; -(void)setValue:(id)value forKey:(NSString *)key;
valueForKey的方法根據(jù)key的值讀取對(duì)象的屬性,setValue:forKey:是根據(jù)key的值來寫對(duì)象的屬性。
注意:
(1). key的值必須正確,如果拼寫錯(cuò)誤,會(huì)出現(xiàn)異常
(2). 當(dāng)key的值是沒有定義的,valueForUndefinedKey:這個(gè)方法會(huì)被調(diào)用,如果你自己寫了這個(gè)方法,key的值出錯(cuò)就會(huì)調(diào)用到這里來
(3). 因?yàn)轭恔ey反復(fù)嵌套,所以有個(gè)keyPath的概念,keyPath就是用.號(hào)來把一個(gè)一個(gè)key鏈接起來,這樣就可以根據(jù)這個(gè)路徑訪問下去
(4). NSArray/NSSet等都支持KVC
KVC過程講解:
以?[object setValue:@"134567" forKey:@"uid"];為例子,來探究KVC的實(shí)現(xiàn)過程
第一步:搜索
1、首先搜索setKey:方法.(key指成員變量名, 首字母大寫)
2、上面的setter方法沒找到, 如果類方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的順序搜索成員名.(NSKeyValueCodingCatogery中實(shí)現(xiàn)的類方法, 默認(rèn)實(shí)現(xiàn)為返回YES)
3、如果沒有找到成員變量, 調(diào)用setValue:forUnderfinedKey:
第二步:編譯器處理
被編譯器處理后:
// 首先找到對(duì)應(yīng)sel
SEL sel = sel_get_uid("setValue:forKey:");
// 根據(jù)object->isa找到sel對(duì)應(yīng)的IMP實(shí)現(xiàn)指針
IMP method = objc_msg_lookup (object->isa,sel);
// 調(diào)用指針完成KVC賦值
method(object, sel, @"134567", @"uid");
解釋:
1.先根據(jù)方法名通過C函數(shù)sel_get_uid拿到選擇子sel
2.使用C函數(shù)objc_msg_lookup通過對(duì)象指針,選擇子獲取函數(shù)實(shí)現(xiàn)指針
3. 調(diào)用C函數(shù)method(object, sel, @"134567", @"uid"),實(shí)現(xiàn)KVC賦值
引申:setValue和setObject區(qū)別
setObject:ForKey: 是NSMutableDictionary特有的;setValue:ForKey:是KVC的主要方法
setobject中的key和value可以為除了nil外的任何對(duì)象
setValue中的key只能為字符串 value可以為nil也可以為空對(duì)象[NSNull null]以及全部對(duì)象