IOS KVO原理解析與應(yīng)用
一、KVO概述
KVO,即:Key-Value Observing,是Objective-C對觀察者模式的實現(xiàn),每次當(dāng)被觀察對象的某個屬性值發(fā)生改變時,注冊的觀察者便能獲得通知,這種模式有利于兩個類間的解耦合,尤其是對于業(yè)務(wù)邏輯與視圖控制 這兩個功能的解耦合。
二、KVO有哪些應(yīng)用?
- NSOperation
- NSOperationQueue
- RAC
三、KVO的使用和實現(xiàn)?
1、使用KVO
1.注冊觀察者,指定被觀察對象的屬性:
[_people addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
2.在觀察者中實現(xiàn)以下回調(diào)方法:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSString *name = [object valueForKey:@"name"];
NSLog(@"new name is: %@", name);
}
只要People對象中的name屬性發(fā)生變化,系統(tǒng)會自動調(diào)用該方法。
3.最后不要忘了在dealloc中移除觀察者
[_people removeObserver:self forKeyPath:@"age"];
2、KVO的實現(xiàn)
KVO 在apple文檔的說明
Automatic key-value observing is implemented using a technique called
isa-swizzling… When an observer is registered for an attribute of an object the
isa pointer of the observed object is modified, pointing to an intermediate class
rather than at the true class …
利用運行時,生成一個對象的子類,并生成子類對象,并替換原來對象的isa指針,重寫了set方法。
讓我們看看代碼
- 這是我們創(chuàng)建的
Myprofile類
@interface MyProfile : NSObject
@property (nonatomic,strong) NSString *avatar;
@property (nonatomic,strong) NSString *age;
@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSMutableArray *dataArr;
@property (nonatomic,strong) MyDetail *myDetail;
- 再看
viewcontroller
self.myprofile = [[MyProfile alloc]init];
self.myprofile.name = @"sallen";
NSLog(@"before:%s",object_getClassName(self.myprofile));
[self.myprofile addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.myprofile.name = @"slim";
NSLog(@"after:%s",object_getClassName(self.myprofile));
1.通過打印可以看出class明顯發(fā)生了變化,監(jiān)聽之后的class替換了原有class的isa指針
2.再看看子類
self.myprofile = [[MyProfile alloc]init];
self.myprofile.name = @"sallen";
NSLog(@"before:%@",[self findSubClass:[self.myprofile class]]);
[self.myprofile addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
self.myprofile.name = @"slim";
NSLog(@"after:%@",[self findSubClass:[self.myprofile class]]);
通過打印可以看出明顯多了個子類
3.對于容器的監(jiān)聽
[self.myprofile addObserver:self forKeyPath:@"dataArr" options:NSKeyValueObservingOptionNew context:nil];
[self.myprofile.dataArr addObject:@"slim"];
通過監(jiān)聽數(shù)組發(fā)現(xiàn),是沒有觸發(fā)的通知的,因為重寫了set方法。
我們可以利用kvc實現(xiàn)對數(shù)組的監(jiān)聽
[[self.myprofile mutableArrayValueForKeyPath:@"dataArr"] addObject:@"slim"];
4.多級路徑屬性
Myprofile類里又包含了MyDetail類
Mydetail創(chuàng)建了content屬性
如果我們需要監(jiān)聽myDetail屬性的變化
我們在Myprofile.m通過方法:+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key,
一個Key觀察多個屬性值的改變。
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet *keySet = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"myDetail"]) {
NSSet *set = [NSSet setWithObject:@"_myDetail.content"];
keySet = [keySet setByAddingObjectsFromSet:set];
}
return keySet;
}
打印結(jié)果:
四、KVO的缺陷
KVO很強大,但是也有缺點
</br>
1.只能重寫 -observeValueForKeyPath:ofObject:change:contex這個方法 來獲得通知,不能使用自定義的selector, 想要傳一個block更是不可能 ,而且還要處理父類的情況 父類同樣觀察一個同樣的屬性的情況 ,但是有時候并不知道父類 是不是對這個消息有興趣。
</br>
2.父類和子類同時存在KVO時,很容易出現(xiàn)對同一個keyPath進行兩次removeObserver操作,從而導(dǎo)致程序crash。要避免這個問題,就需要區(qū)分出KVO是self注冊的,還是superClass注冊的,我們可以在 -addObserver:forKeyPath:options:context:和-removeObserver:forKeyPath:context這兩個方法中傳入不同的context進行區(qū)分。