addObserver:forKeyPath:options:context:各個(gè)參數(shù)的作用分別是什么, observer中需要實(shí)現(xiàn)哪個(gè)方法才能獲得KVO回調(diào)?
/**
1\. self.person:要監(jiān)聽的對(duì)象
2\. 參數(shù)說明:
* @param addObserver 觀察者,負(fù)責(zé)處理監(jiān)聽事件的對(duì)象
* @param forKeyPath 要監(jiān)聽的屬性
* @param options 觀察的選項(xiàng)(觀察新、舊值,也可以都觀察)
* @param context 上下文,用于傳遞數(shù)據(jù),可以利用上下文區(qū)分不同的監(jiān)聽
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
/**
* 當(dāng)監(jiān)控的某個(gè)屬性的值改變了就會(huì)調(diào)用
*
* @param keyPath 監(jiān)聽的屬性名
* @param object 屬性所屬的對(duì)象
* @param change 屬性的修改情況(屬性原來的值`oldValue`、屬性最新的值`newValue`)
* @param context 傳遞的上下文數(shù)據(jù),與監(jiān)聽的時(shí)候傳遞的一致,可以利用上下文區(qū)分不同的監(jiān)聽
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@對(duì)象的%@屬性改變了:%@", object, keyPath, change);
}
一、KVO (Key-Value Observing)
KVO 是 Objective-C 對(duì)觀察者模式(Observer Pattern)的實(shí)現(xiàn)。也是 Cocoa Binding 的基礎(chǔ)。當(dāng)被觀察對(duì)象的某個(gè)屬性發(fā)生更改時(shí),觀察者對(duì)象會(huì)獲得通知。
有意思的是,你不需要給被觀察的對(duì)象添加任何額外代碼,就能使用 KVO 。這是怎么做到的?
二、 KVO內(nèi)部實(shí)現(xiàn)原理
- KVO是基于runtime機(jī)制實(shí)現(xiàn)的
- 當(dāng)某個(gè)類的屬性對(duì)象
第一次被觀察時(shí),系統(tǒng)就會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建該類的一個(gè)派生類,在這個(gè)派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內(nèi)實(shí)現(xiàn)真正的通知機(jī)制 - 如果原類為Person,那么生成的派生類名為
NSKVONotifying_Person - 每個(gè)類對(duì)象中都有一個(gè)isa指針指向當(dāng)前類,當(dāng)一個(gè)類對(duì)象的第一次被觀察,那么系統(tǒng)會(huì)偷偷將isa指針指向動(dòng)態(tài)生成的派生類,從而在給被監(jiān)控屬性賦值時(shí)執(zhí)行的是派生類的setter方法
- 鍵值觀察通知依賴于NSObject 的兩個(gè)方法:
willChangeValueForKey:和didChangevlueForKey:;在一個(gè)被觀察屬性發(fā)生改變之前,willChangeValueForKey:一定會(huì)被調(diào)用,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后,didChangeValueForKey:會(huì)被調(diào)用,繼而observeValueForKey:ofObject:change:context:也會(huì)被調(diào)用。 - 補(bǔ)充:KVO的這套實(shí)現(xiàn)機(jī)制中蘋果還偷偷重寫了class方法,讓我們誤認(rèn)為還是使用的當(dāng)前類,從而達(dá)到隱藏生成的派生類

image.png
三、如何手動(dòng)觸發(fā)一個(gè)value的KVO
- 自動(dòng)觸發(fā)的場(chǎng)景:在注冊(cè)KVO之前設(shè)置一個(gè)初始值,注冊(cè)之后,設(shè)置一個(gè)不一樣的值,就可以觸發(fā)了
- 想知道如何手動(dòng)觸發(fā),必須知道自動(dòng)觸發(fā) KVO 的原理,見上面的描述
- 手動(dòng)觸發(fā)演示
@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad
{
[super viewDidLoad];
// “手動(dòng)觸發(fā)self.now的KVO”,必寫。
[self willChangeValueForKey:@"now"];
// “手動(dòng)觸發(fā)self.now的KVO”,必寫。
[self didChangeValueForKey:@"now"];
}

四、補(bǔ)充: 如何關(guān)閉默認(rèn)的KVO的默認(rèn)實(shí)現(xiàn),并進(jìn)入自定義的KVO實(shí)現(xiàn)?(看鏈接)
五、附注: KVC底層實(shí)現(xiàn)原理(如下)
KVC運(yùn)用了一個(gè)isa-swizzling技術(shù). isa-swizzling就是類型混合指針機(jī)制, 將2個(gè)對(duì)象的isa指針互相調(diào)換, 就是俗稱的黑魔法.
KVC主要通過isa-swizzling, 來實(shí)現(xiàn)其內(nèi)部查找定位的. 默認(rèn)的實(shí)現(xiàn)方法?由NSOject提供isa指針, 如其名稱所指,(就是is a kind of的意思), 指向分發(fā)表對(duì)象的類. 該分發(fā)表實(shí)際上包含了指向?qū)崿F(xiàn)類中的方法的指針, 和其它數(shù)據(jù)。
- 具體主要分為三大步
- 第一步:尋找該屬性有沒有setsetter方法?有,就直接賦值
- 第二步:尋找有沒有該屬性帶下劃線的成員屬性?有,就直接賦值
- 第三步:尋找有沒有該屬性的成員屬性?有,就直接賦值
- 或者這么說
- 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:
比如說如下的一行KVC的代碼:
-
舉個(gè)??e.g:
[object setValue:@"13123" forKey:@"uuid"];
就會(huì)被編譯器處理成:
// 首先找到對(duì)應(yīng)sel
SEL sel = sel_get_ uuid("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, @"13123", @"uuid");