前言
KVO:簡(jiǎn)單的來(lái)說(shuō),就是觀察者觀察被觀察對(duì)象屬性的變化而發(fā)生相應(yīng)的變化曲管。實(shí)現(xiàn)的原理基于KVC與強(qiáng)大的Runtime機(jī)制篙议。原理是什么耿芹?如何實(shí)現(xiàn)的恶导?
系統(tǒng)實(shí)現(xiàn)步驟:
以下大概分為三步:
假設(shè)有類Person,它擁有一個(gè)年齡屬性age浆竭。那么當(dāng)Person類的對(duì)象第一次被觀察的時(shí)候,系統(tǒng)會(huì)在運(yùn)行期動(dòng)態(tài)創(chuàng)建Person的派生類惨寿。我們?nèi)绾沃腊钚梗肯旅嫖覀兏鶕?jù)斷點(diǎn)查看控制臺(tái)即可。
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
上述代碼執(zhí)行前:
上述代碼執(zhí)行后:
通過(guò)以上我們可以得知在系統(tǒng)在運(yùn)行期又動(dòng)態(tài)的創(chuàng)建Person的派生類叫做NSKVONOtifying_Person.
在派生類NSKVONOtifying_Person中重寫(xiě)Person類的set方法裂垦,NSKVONOtifying_Person類在被重寫(xiě)的set方法中實(shí)現(xiàn)通知機(jī)制顺囊。此時(shí)類NSKVONOtifying_Person重寫(xiě)class方法,并且系統(tǒng)將所有原本指向類Person對(duì)象的isa指針指向類NSKVONOtifying_Person對(duì)象缸废。
最后當(dāng)Person類對(duì)象調(diào)用其set方法時(shí),實(shí)質(zhì)就是NSKVONOtifying_Person調(diào)用了重寫(xiě)的set方法驶社,在set方法里用super關(guān)鍵字調(diào)用其父類的set方法企量,而后調(diào)用-(void)observeValueForKeyPath:ofObject:change:context:作出通知響應(yīng)。
自定義實(shí)現(xiàn)
了解以上原理后亡电,我們可以自己嘗試實(shí)現(xiàn)
- 創(chuàng)建NSObject的分類届巩,自定義方法,在方法中創(chuàng)建中間派生類
- 重寫(xiě)set方法
- set方法中通知調(diào)用
下面貼上主代碼:
NSObject+KVO.m中:
- (void)zz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
self.zz_observer = observer;
self.zz_keyPath = keyPath;
NSString * className = [NSString stringWithFormat:@"ZZKVONotifying_%@",NSStringFromClass(self.class)];
const char *cla = className.UTF8String;
Class subP = objc_allocateClassPair([self class], cla, 0);
class_addMethod(subP, @selector(setAge:), (IMP)setAge, "v@:@");
objc_registerClassPair(subP);
object_setClass(self, subP);
}
void setAge(id self , SEL _cmd,NSUInteger age){
NSString *keyPath = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observeKeyPathKey));
NSObject *obj = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(observeKey));
[self willChangeValueForKey:keyPath];
/*這里還應(yīng)該調(diào)用父親的[super setAge:age]方法*/
[self didChangeValueForKey:keyPath];
[obj observeValueForKeyPath:keyPath ofObject:self change:@{@"new":[NSNumber numberWithInteger:age]} context:nil];
}
通過(guò)以上份乒,就實(shí)現(xiàn)了簡(jiǎn)單的KVO監(jiān)聽(tīng)屬性變化響應(yīng)的功能恕汇。
PS
當(dāng)然系統(tǒng)調(diào)用原比這要復(fù)雜的多。當(dāng)添加觀察者后或辖,方法內(nèi)部需要做很多的安全判斷瘾英,如該對(duì)象是否實(shí)現(xiàn)了屬性的set、get方法颂暇,如果 沒(méi)有就拋出異常缺谴;是否已經(jīng)存在該派生類對(duì)象,如果沒(méi)有創(chuàng)建如果有就返回等等耳鸯。另外湿蛔,同一個(gè)對(duì)象的屬性可以有多個(gè)觀察者,所以內(nèi)部必須要有一個(gè)集合去記錄县爬,當(dāng)發(fā)生變化時(shí)阳啥,需要各個(gè)通知一一回調(diào)。
完結(jié)
文章中的代碼只展示主要模塊财喳,如需要完整demo察迟,請(qǐng)點(diǎn)我自行下載。