
一、是個啥
KVO全稱為Key Value Observing,鍵值監(jiān)聽,可以用于監(jiān)聽某個對象屬性值的改變。
是觀察者設計模式的一種實現(xiàn)。
二、用法
簡單試用
#pragma mark - KVO
-(void)KVOTest{
self.person = [[Person alloc] init];
self.person.age = 10;
//給person對象添加監(jiān)聽對象
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:@"123"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.age = 20;
}
//響應方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//觀察者觀察name的變化,當點擊屏幕,改變name的值,chang就會捕獲新值
NSLog(@"%@-%@-%@-%@",keyPath,object,change,context);
}
//移除監(jiān)聽
-(void)dealloc{
[self.person removeObserver:self forKeyPath:@"age"];
}
三、KVO的底層是怎么實現(xiàn)的?
self.xiaoming1 = [[XiaoMing alloc] init];
self.xiaoming1.age = 1;
self.xiaoming2 = [[XiaoMing alloc] init];
self.xiaoming2.age = 2;
[self.xiaoming1 addObserver:self //本控制器來監(jiān)聽
forKeyPath:@"age"http://監(jiān)聽xiaoming1 age 的變化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常選這2個,下面會解釋
context:@""];//下面會演示
1、疑問
走的都是XiaoMing類方法setAge,為什么xiaoming1的age值,VC能監(jiān)聽到值的改變,xiaoming2不能?
這不是setAge的問題,是對象的問題
我們來打印一下這2個對象的isa,發(fā)現(xiàn)不一樣

2、本質分析
未使用kvo監(jiān)聽的對象

使用kvo監(jiān)聽的對象

NSKVONotifying_XiaoMing是使用runtime動態(tài)創(chuàng)建的一個類,這就是OC強大的地方,可以在運行的過程中,自己創(chuàng)建一個類。
NSKVONotifying_XiaoMing是XiaoMing的子類。
3、本質驗證
打印一下xiaoming1 、xiaoming2的類對象
NSLog(@"??監(jiān)聽之前--xiaoming1的類對象:%@-xiaoming2的類對象:%@",object_getClass(self.xiaoming1),
object_getClass(self.xiaoming2));
[self.xiaoming1 addObserver:self //本控制器來監(jiān)聽
forKeyPath:@"age"http://監(jiān)聽xiaoming1 age 的變化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常選這2個,下面會解釋
context:@""];//下面會演示
NSLog(@"??監(jiān)聽之后--xiaoming1的類對象:%@-xiaoming2的類對象:%@",object_getClass(self.xiaoming1),
object_getClass(self.xiaoming2));

打印一下xiaoming1 、xiaoming2的setAge方法實現(xiàn)
NSLog(@"??監(jiān)聽之前--方法實現(xiàn):%p-%p",[self.xiaoming1 methodForSelector:@selector(setAge:)],
[self.xiaoming2 methodForSelector:@selector(setAge:)]);
[self.xiaoming1 addObserver:self //本控制器來監(jiān)聽
forKeyPath:@"age"http://監(jiān)聽xiaoming1 age 的變化
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld//通常選這2個,下面會解釋
context:@""];//下面會演示
NSLog(@"??監(jiān)聽之后--方法實現(xiàn):%p-%p",[self.xiaoming1 methodForSelector:@selector(setAge:)],
[self.xiaoming2 methodForSelector:@selector(setAge:)]);

再來看一下方法實現(xiàn)

打印一下類對象和元類對象
NSLog(@"??xiaoming1:類對象-%@,元類對象-%@",object_getClass(self.xiaoming1),object_getClass(object_getClass(self.xiaoming1)));
NSLog(@"??xiaoming2:類對象-%@,元類對象-%@",object_getClass(self.xiaoming2),object_getClass(object_getClass(self.xiaoming2)));

4、Foundation里的_NSSetValueAndNotify的實現(xiàn)
如果會點逆向的知識可能會好探索一些,這里直接說結論
打印調用順序
-(void)setAge:(int)age{
_age = age;
NSLog(@"setAge");
}
-(void)willChangeValueForKey:(NSString *)key{
[super willChangeValueForKey:key];//為了干擾以前的實現(xiàn),調用super
NSLog(@"willChangeValueForKey");
}
-(void)didChangeValueForKey:(NSString *)key{
NSLog(@"didChangeValueForKey-開始");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey-結束");
}

四、問題
1、iOS用什么方式實現(xiàn)對一個對象的KVO?(KVO的本質是什么?)
1、使用runtime API動態(tài)生產(chǎn)一個子類,并且讓實例對象的isa指向這個全新的
子類
2、當修改對象的屬性時,會調用Foundation里的_NSSetXXXXAndNotify的函數(shù)
- willChangeValueForKey
- 父類原來的setter
- didChangeValueForKey
- 內部會觸發(fā)監(jiān)聽器observe的監(jiān)聽方法:observeValueForKeyPath
2、如何手動觸發(fā)KVO?
這個題的理解應該是不改變對象的屬性值,也能觸發(fā)KVO
手動調用willChangeValueForKey和didChangeValueForKey方法。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.xiaoming1 willChangeValueForKey:@"age"];
[self.xiaoming1 didChangeValueForKey:@"age"];
}

3、直接修改成員變量會不會觸發(fā)KVO?
不會
4、KVO的運用場景?