KVO全稱為Key Value Observing井辜,鍵值監(jiān)聽機制,由NSKeyValueObserving協(xié)議提供支持管闷,NSObject類繼承了該協(xié)議粥脚,所以NSObject的子類都可使用該方法。
KVO使用步驟
1.注冊觀察者(為被觀察這指定觀察者以及被觀察者屬性)
創(chuàng)建一個Person對象包个,寫一個age屬性刷允,為age屬性添加KVO監(jiān)聽
/*
options: 有4個值,分別是:
NSKeyValueObservingOptionOld 把更改之前的值提供給處理方法
NSKeyValueObservingOptionNew 把更改之后的值提供給處理方法
NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法碧囊,一旦注冊树灶,立馬就會調(diào)用一次。通常它會帶有新值糯而,而不會帶有舊值天通。
NSKeyValueObservingOptionPrior 分2次調(diào)用。在值改變之前和值改變之后熄驼。
*/
//注冊一個監(jiān)聽器用于監(jiān)聽指定的key路徑
[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"];
2.實現(xiàn)回調(diào)方法
// 當監(jiān)聽對象的屬性值發(fā)生改變時像寒,就會調(diào)用
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"監(jiān)聽到%@的%@屬性值改變了 - %@ - %@", object, keyPath, change, context);
}
3.修改需要監(jiān)聽的屬性值烘豹,查看是否監(jiān)聽成功
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 10;
}
使用注意點
[self.person addObserver:self forKeyPath:@"age" options:options context:@"123"] ;這行代碼中context:@"123"
這個參數(shù)傳的值都會
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}傳到回調(diào)方法中context
中來,用途: 如果說在一個控制器中我們監(jiān)聽了多個屬性的變化诺祸,而每一個屬性的變化之后對應(yīng)的事件處理也是不一樣的携悯,那么在注冊觀察者時context 傳不同的字符串就可以區(qū)分判斷了
思考:為什么注冊觀察者之后,修改觀察對象屬性的值筷笨,就會走回調(diào)方法憔鬼?KVO是怎么實現(xiàn)這個功能的呢?
帶著這兩個問題下面來一探究竟
- OC是消失機制當我們調(diào)用 self.person1.age = 10; 這行代碼時實際就是[self.person setAge:10] 然后再轉(zhuǎn)化成objc_msgsend(person,@selector(setAge)),
那么是不是KVO在setAge 這個方法中做了文章呢胃夏?
前幾篇文章已經(jīng)寫到對象方法存儲到類對象中轴或,那么在控制臺輸入 po self.person.isa 發(fā)現(xiàn)結(jié)果并不是 Person 而是 NSKVONotifying_Person 這說明KVO在運行時新建了一個NSKVONotifying_Person類,將person的isa指針指向這個類
, 在控制臺輸入 po [self.person class]發(fā)現(xiàn)輸出的是Person
而不是NSKVONotifying_Person
。這是為什么仰禀?
猜測:系統(tǒng)在運行時照雁,KVO動態(tài)創(chuàng)建一個NSKVONotifying_Person類,將person的isa指針指向這個類悼瘾。新創(chuàng)建的NSKVONotifying_Person 繼承與Person 并且重寫了 setAge方法和 class方法
驗證1:NSKVONotifying_Person是否繼承自Person囊榜,是否重寫了Class方法
#import <objc/runtime.h>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person1.age = 20;
//獲取類對象
Class personClass = object_getClass(self.person1);
//通過類對象獲取父類
Class personClass02 = class_getSuperclass(personClass);
NSLog(@"%@-------%@",personClass02,personClass03);
}
輸出 NSKVONotifying_Person-------Person
通過這幾行代碼就可以證明NSKVONotifying_Person 繼承自 Person
- 細節(jié)
再獲取類對象時可以使用object_getClass 也可以使用[A class],上面為什么使用了object_getClass而不是使用[A class]呢?
答案: NSKVONotifying_Person 重寫了class方法亥宿,[person class]返回的并不是原對象而是原對象的父類也就是Person類卸勺,如果沒有重寫的話,返回person的isa指針指向的類打印結(jié)果應(yīng)該是NSKVONotifyin_Person烫扼,但是蘋果官方不希望將NSKVONotifyin_Person類的內(nèi)部實現(xiàn)暴露出來曙求,所以在內(nèi)部重寫了class方法,直接返回Person類映企,所以我們在調(diào)用person的class方法時悟狱,返回的是Person類。
驗證2:NSKVONotifying_Person是否重寫了setAge方法
//新建另外一個person2對象堰氓,不添加監(jiān)聽
self.person2 = [[Person alloc] init];
self.person2.age = 2;
//點擊修改person2的值
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
Class personClass = object_getClass(self.person);
Class personClass02 = object_getClass(personClass);
Class personClass03 = class_getSuperclass(personClass);
//修改person2的值
self.person2.age = 20;
}
答案:person2 在調(diào)用setAge方法時并沒有觸發(fā)回調(diào)方法挤渐,而添加了觀察者的person對象在調(diào)用setAge 方法時觸發(fā)了方法,說明NSKVONotifying_Person 重寫了setAge方法
思考1:NSKVONotifying_Person怎么重寫了setAge方法以實現(xiàn)觸發(fā)回調(diào)
經(jīng)過查看底層源碼和相關(guān)資料分析們可以知道双絮,NSKVONotifyin_Person
中的setAge
方法中其實調(diào)用了Fundation
框架中C語言函數(shù)_NSsetIntValueAndNotify
浴麻,而_NSsetIntValueAndNotify
內(nèi)部做的操作相當于,首先調(diào)用willChangeValueForKey
方法囤攀,之后調(diào)用父類的setAge方法對成員變量賦值软免,最后調(diào)用didChangeValueForKey
方法。其中didChangeValueForKey
中會調(diào)用監(jiān)聽器的監(jiān)聽方法焚挠,最終來到監(jiān)聽者的observeValueForKeyPath
方法膏萧。
思考2:NSKVONotifyin_Person的內(nèi)部結(jié)構(gòu)是怎樣的?
NSKVONotifyin_Person
作為Person
的子類,其superclass
指針指向Person
類榛泛,并且NSKVONotifyin_Person
內(nèi)部的setAge方法做了單獨的實現(xiàn)蝌蹂。我們可以通過runtime
的方法去分別打印person1
和person2
兩個對象和NSKVONotifyin_Person
類對象內(nèi)存儲的對象方法:
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 給person1對象添加KVO監(jiān)聽
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
[self printMethods: object_getClass(self.person1)];
[self printMethods: object_getClass(self.person2)];
[self.person1 removeObserver:self forKeyPath:@"age"];
}
- (void) printMethods:(Class)cls
{
unsigned int count ;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
[methodNames appendFormat:@"%@ : ", cls];
for (int i = 0 ; i < count; i++) {
Method method = methods[I];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString: methodName];
[methodNames appendString:@" "];
}
NSLog(@"%@",methodNames);
free(methods);
}
打印輸出:
Person : setAge:, age,
NSKVONotifying_Person : setAge:, class, dealloc, _isKVOA,
NSKVONotifyin_Person的內(nèi)存結(jié)構(gòu)及方法調(diào)用順序
總結(jié):
1、KVO的本質(zhì)是什么挟鸠?
當我們給對象注冊一個觀察者添加了KVO監(jiān)聽時叉信,系統(tǒng)會修改這個對象的isa指針指向亩冬。在運行時艘希,動態(tài)創(chuàng)建一個新的子類,NSKVONotifying_A類硅急,將A的isa指針指向這個子類覆享,來重寫原來類的set方法;set方法實現(xiàn)內(nèi)部會順序調(diào)用willChangeValueForKey方法营袜、原來的setter方法實現(xiàn)撒顿、didChangeValueForKey方法,而didChangeValueForKey方法內(nèi)部又會調(diào)用監(jiān)聽器的observeValueForKeyPath:ofObject:change:context:監(jiān)聽方法荚板。
2凤壁、如何手動觸發(fā)KVO?
實現(xiàn)調(diào)用willChangeValueForKey和didChangeValueForKey方法跪另。
如有疑問:
iOS OC對象的本質(zhì)窺探(一)
iOS OC對象的本質(zhì)窺探(對象分類)(二)
特別推薦:
iOS獲取手機唯一標示
iOS 高德地圖實現(xiàn)大頭針展示拧抖,分級大頭針,自定制大頭針免绿,在地圖上畫線唧席,線和點共存,路線規(guī)劃(駕車路線規(guī)劃)嘲驾,路線導(dǎo)航淌哟,等一些常見的使用場景