注:文章為自己學(xué)習(xí)李明杰老師的OC底層視頻做的隨手筆記
什么是KVO?
KVO的全稱是key-value obsering 俗稱"鍵值監(jiān)聽",可以用于監(jiān)聽某個(gè)對(duì)象屬性值的改變
首先一個(gè)最簡(jiǎn)單的例子實(shí)現(xiàn)kvo的使用
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.p1 = [[Person alloc] init];
[self.p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
self.p2 = [[Person alloc] init];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"監(jiān)聽到了%@的值改變?yōu)榱?@",object,change);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"設(shè)置p1的age屬性");
self.p1.age = 10;
NSLog(@"設(shè)置p2的age屬性");
self.p2.age = 20;
}
當(dāng)我們點(diǎn)擊手機(jī)屏幕的時(shí)候,把p1的age值修改為10,就監(jiān)聽到了age值的改變
2019-12-09 14:45:32.554562+0800 kvoDemo[15802:2077682] 設(shè)置p1的age屬性
2019-12-09 14:45:32.555226+0800 kvoDemo[15802:2077682] 執(zhí)行了set方法,值為10
2019-12-09 14:45:32.555548+0800 kvoDemo[15802:2077682] 監(jiān)聽到了<Person: 0x281394990>的值改變?yōu)榱藍(lán)
kind = 1;
new = 10;
}
2019-12-09 14:45:32.555645+0800 kvoDemo[15802:2077682] 設(shè)置p2的age屬性
2019-12-09 14:45:32.555720+0800 kvoDemo[15802:2077682] 執(zhí)行了set方法,值為20
可以看出,同時(shí)修改了p1,p2的age值,但是只有p1觸發(fā)了監(jiān)聽方法,我們知道,.age設(shè)置屬性,實(shí)際上是調(diào)用屬性的set方法,都是調(diào)用person類里面的age的同一個(gè)set方法,為什么p1添加了監(jiān)聽,就可以監(jiān)聽到屬性值變化,p2就沒有,這個(gè)中間又發(fā)生了什么?
打印p1,p2的isa指針
(lldb) po self.p1->isa
NSKVONotifying_Person
(lldb) po self.p2->isa
Person
發(fā)現(xiàn)p1的isa指針指向變了,并沒有指向person的class對(duì)象,而是指向了NSKVONotifying_Person類對(duì)象,這是runtime在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的一個(gè)類,NSKVONotifying_Person是person的一個(gè)子類,他的superclass指針指向person的類對(duì)象,set方法會(huì)調(diào)用Foundation里的方法
用代碼驗(yàn)證下,看下添加kvo前后,屬性的set方式地址有沒有變化
NSLog(@"添加kvo之前,p1的setAge方法的地址---%p,p1的setAge方法的地址---%p",[self.p1 methodForSelector:@selector(setAge:)],[self.p2 methodForSelector:@selector(setAge:)]);
[self.p1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"添加kvo之后,p1的setAge方法的地址---%p,p1的setAge方法的地址---%p",[self.p1 methodForSelector:@selector(setAge:)],[self.p2 methodForSelector:@selector(setAge:)]);
2019-12-09 15:36:40.494175+0800 kvoDemo[15928:2090439] 添加kvo之前,p1的setAge方法的地址---0x102d4dfa8,p1的setAge方法的地址---0x102d4dfa8
2019-12-09 15:36:40.494402+0800 kvoDemo[15928:2090439] 添加kvo之后,p1的setAge方法的地址---0x186739dbc,p1的setAge方法的地址---0x102d4dfa8
看到p1的地址發(fā)生了變動(dòng),p2沒有發(fā)生改變
查看添加監(jiān)聽前后setAge的實(shí)現(xiàn)
(lldb) p (IMP)0x1040b9fa8
(IMP) $0 = 0x00000001040b9fa8 (kvoDemo`-[Person setAge:] at Person.m:15)
(lldb) p (IMP)0x186739dbc
(IMP) $1 = 0x0000000186739dbc (Foundation`_NSSetLongLongValueAndNotify)
很明顯添加監(jiān)聽后,setAge的方法實(shí)現(xiàn)改變了,變成了NSSetLongLongValueAndNotify方法實(shí)現(xiàn)
驗(yàn)證:
重寫這兩個(gè)方法
執(zhí)行后結(jié)果:
可以發(fā)現(xiàn)是先執(zhí)行了willChangeValueForKey,然后又執(zhí)行原來的set方法,然后調(diào)用didChangeValueForKey---begin,然后 [super didChangeValueForKey:key];調(diào)用監(jiān)聽器的監(jiān)聽方法,最后結(jié)束didChangeValueForKey---end
面試題
ios用什么方式實(shí)現(xiàn)一個(gè)對(duì)象的kvo?(kvo的本質(zhì)是什么?)
1.利用runtime的動(dòng)態(tài)生成一個(gè)子類,并且讓實(shí)例對(duì)象的isa指向全新的子類
2.當(dāng)修改實(shí)例對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的NSSet***ValueAndNotify函數(shù),函數(shù)內(nèi)部調(diào)用willChangeValueForKey,然后調(diào)用父類原來的setter方法,.然后調(diào)用didChangeValueForKey方法.didChangeValueForKey內(nèi)部觸發(fā)監(jiān)聽器的observeValueForKeyPath方法
如何手動(dòng)觸發(fā)KVO?
手動(dòng)調(diào)用下面兩個(gè)方法
[self.p1 willChangeValueForKey:@"age"];
[self.p1 didChangeValueForKey:@"age"];
``