iOS設(shè)計模式 —— KV0
刨根問底KVO
KVO 全稱 Key-Value Observing。中文叫鍵值觀察。KVO其實是一種觀察者模式,觀察者在鍵值改變時會得到通知,利用它可以很容易實現(xiàn)視圖組件和數(shù)據(jù)模型的分離,當(dāng)數(shù)據(jù)模型的屬性值改變之后作為監(jiān)聽器的視圖組件就會被激發(fā),激發(fā)時就會回調(diào)監(jiān)聽器自身。相比Notification,KVO更加的簡單直接。
KVO的操作方法由NSKeyValueCoding提供,而他是NSObject的類別,也就是說ObjC中幾乎所有的對象都支持KVO操作。
KVO的使用也很簡單,就是簡單的3步。
注冊需要觀察的對象的屬性addObserver:forKeyPath:options:context:
實現(xiàn)observeValueForKeyPath:ofObject:change:context:方法,這個方法當(dāng)觀察的屬性變化時會自動調(diào)用.在這個方法中還通過NSKeyValueObservingOptionNew這個參數(shù)要求把新值在dictionary中傳遞過來。
取消注冊觀察removeObserver:forKeyPath:context:
我們觀察下代碼實現(xiàn),探究實現(xiàn)原理:
Person.h
1
2
3
4
5
6
7
8
9
#import
@interfacePerson:NSObject
- (void)registerObserver;
@property(nonatomic,assign)NSIntegerage;
@end
Person.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import"Person.h"
@implementationPerson
- (void)registerObserver {
[selfaddObserver:selfforKeyPath:@"age"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOldcontext:nil];
}
- (NSString*)description {
return[NSStringstringWithFormat:@"%@,%ld",[selfvalueForKey:@"isa"],self.age];
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {
if([keyPath isEqualToString:@"age"]) {
NSLog(@"%@",change);
}else{
[superobserveValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc {
[selfremoveObserver:selfforKeyPath:@"age"];
}
main函數(shù)
1
2
3
4
5
6
7
8
Person *p =[[Person alloc] init];
p.age =20;
NSLog(@"%@",p);
[p registerObserver];
p.age =30;
NSLog(@"%@",p);
運行程序打印出的log日志為:
1
2
3
4
5
6
7
Person,20
{
kind =1;
new =30;
old =20;
}
NSKVONotifying_Person,30
不要太驚訝,我們慢慢解釋。
我重寫的Person類的description方法,使用KVC拿到isa指針,獲取到self的類名,我們發(fā)現(xiàn)在使用KVO之前類名是Person,但注冊了KVO之后,類名變成了NSKVONotifying_Person。
主要是因為KVO的實現(xiàn)使用了isa-swizzling。在程序運行時Person會生成一個派生類NSKVONotifying_Person,在這個派生類中重寫基類中任何被觀察屬性的setter方法,用來欺騙系統(tǒng)頂替原先的類。在setter方法中實現(xiàn)真正的通知機制.
1
2
3
4
5
6
7
//可以到偽代碼
- (void)setAge:(int)age
{
[supersetAge:age];
[監(jiān)聽器observeValueForKeyPath:@"age"ofObject:selfchange:@{}context:nil];
}
我們又可以猜測,使用KVO,內(nèi)部一定執(zhí)行setter方法。
當(dāng)我們把上面代碼p.age = 30;改成p->_age = 30;
你會發(fā)現(xiàn)KVO的方法不走了,也證實了這點。
如果p不提供setAge,getAge方法,還想用KVO.
1
2
3
[pwillChangeValueForKey:@"age"];
p->_age =30;
[pdidChangeValueForKey:@"age"];
蘋果官方KVO文檔: