KVO
Key-Value observing(KVC),鍵值觀察女揭,它提供一種機(jī)制屏鳍,當(dāng)被觀察的對(duì)象的屬性被修改后,KVO會(huì)自動(dòng)通知相對(duì)應(yīng)的觀察者挖函。接下來我會(huì)演示一下KVO的例子状植。
觀察Model里的屬性變化
廢話不多說,直接上代碼∨不現(xiàn)有Model: Person
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@end
我們來通過KVO動(dòng)態(tài)監(jiān)聽Person中name, age的變化浅萧。這里是標(biāo)準(zhǔn)的操作流程:
第一步:注冊(cè),指定被觀察者對(duì)象
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
第二步:實(shí)現(xiàn)回調(diào)方法
- (void)observeValueForKeyPath:(NSString *)keyPath? ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
self.titleLabel.text = self.person.name;
}
}
第三步:移除觀察者哲思,如果沒有移除觀察者,會(huì)引發(fā)異常吩案,這也是KVO不方便的一點(diǎn)
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"age"];
如上:實(shí)現(xiàn)這三部棚赔,一個(gè)簡(jiǎn)單的KVO,我們就搞定了徘郭。是不是 so easy!
經(jīng)典案例
下拉刷新靠益,通過監(jiān)聽 frame 來改變動(dòng)畫效果
創(chuàng)建 TableViewController,自定義UIRefreshControl残揉,在這里邊監(jiān)聽 frame胧后,來改變動(dòng)畫效果
[self addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (self.frame.origin.y > 0 ) {return;}
if (self.frame.origin.y < -60 ) {
[UIView animateWithDuration:0.5 animations:^{
self.refreshView.arrowView.transform = CGAffineTransformRotate(self.refreshView.arrowView.transform, M_PI);
self.refreshView.titleLabel.text = @"下拉刷新";
} completion:^(BOOL finished) {
}];
}else if (self.frame.origin.y >= -60){
[UIView animateWithDuration:0.5 animations:^{
self.refreshView.arrowView.transform = CGAffineTransformRotate(self.refreshView.arrowView.transform, M_PI);
self.refreshView.titleLabel.text = @"松手返回";
} completion:^(BOOL finished) {
}];
}
}
關(guān)聯(lián)依賴
是不是說,掌握了第一部分抱环,我們就能是使用KVO了壳快。答案是肯定的,如果各位看官不想在繼續(xù)了解的話镇草,請(qǐng)按左上角【返回】按鈕眶痰。哈哈哈村象,開玩笑啦描馅,接下來給大家介紹一個(gè)好玩的例子,關(guān)聯(lián)依賴母蛛。
廢話不多說因宇,直接上代碼七婴,我就是這么痛快
假設(shè)有Person類,其中有三個(gè)屬性: firstName, lastName, fullName察滑,那當(dāng)我們要?jiǎng)討B(tài)的觀察 fullName時(shí)打厘,怎么辦呢。我們都知道fullName 是跟 firstName, lastName有關(guān)聯(lián)的杭棵,依據(jù)上文說到的方法婚惫,這時(shí)我們要注冊(cè)三個(gè)觀察對(duì)象氛赐,這很low,這時(shí)先舷,依賴關(guān)聯(lián)可以登場(chǎng)了艰管。
第一步: 重寫 + (NSSet *)keyPathsForValuesAffection<鍵名>方法
// 設(shè)置 fullName 依賴 firstName, lastName
// 注冊(cè)觀察 fullName, 當(dāng)firstName, lastName變化時(shí)蒋川,就能收到通知
+ (NSSet *)keyPathsForValuesAffectingFullName {
NSSet *set = [NSSet setWithObjects:@"firstName",@"lastName", nil];
return set;
}
第二步:之后操作同上
[self.person addObserver:self forKeyPath:@"fullName" options: NSKeyValueObservingOptionPrior context:nil];
這時(shí)牲芋,我們?cè)诟淖?firstName, lastName中任意一個(gè)屬性值的時(shí)候,我們通過注冊(cè)的 fullName 都可以獲取到捺球,是不是很神奇案灼帧!
手動(dòng)通知 VS 自動(dòng)通知
有沒有感覺KVO很神奇氮兵,但實(shí)際上發(fā)生的事情是:當(dāng) name 的實(shí)例 -setName: 方法被調(diào)用的時(shí)候裂逐,系統(tǒng)自動(dòng)在之前與之后幫我們添加了部分代碼:
- (void)willChangeValueForKey:(NSString *)key
- (void)didChangeValueForKey: (NSString *)key
他們分別會(huì)在 setName: 中的代碼之前與之后調(diào)用。有些時(shí)候泣栈,我們需要自己來控制是否發(fā)送通知卜高,或者改變某些特殊的數(shù)據(jù)內(nèi)容,就可以做如下操作:
第一步:重寫 setter 方法南片,并手動(dòng)添加通知前后方法
-(void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
// 這里可以對(duì)處理特別操作
_name = [NSString stringWithFormat:@"%@123", name];
[self didChangeValueForKey:@"name"];
}
第二步: 實(shí)現(xiàn) automaticallyNotifiesObserversForKey掺涛,return NO
// 通過此方法,決定是否發(fā)送通知
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// 如果是 name 則疼进,手動(dòng)發(fā)送通知
if ([key isEqualToString:@"name"]) {
return NO;
}
// 其他薪缆,則自動(dòng)發(fā)送通知
return [super automaticallyNotifiesObserversForKey:key];
}
參數(shù)詳解
addObserver:forKeyPath: options:context:
observer:觀察者,一般都是 self(本身)
keyPath: 被觀察的屬性名稱伞广,例如上文中的 @"name", @"age"
options: 觀察屬性的新值拣帽、舊值等的一些配置(枚舉值)
NSKeyValueObservingOptionNew? ? = 0x01,
NSKeyValueObservingOptionOld? ? = 0x02,
NSKeyValueObservingOptionInitial = 0x04,
NSKeyValueObservingOptionPrior? = 0x08
四個(gè)值的含義如下:
NSKeyValueObservingOptionNew:接收方法中使用change參數(shù)傳入變化后的新值,鍵為:NSKeyValueChangeNewKey赔癌;
NSKeyValueObservingOptionOld:接收方法中使用change參數(shù)傳入變化前的舊值诞外,鍵為:NSKeyValueChangeOldKey;
NSKeyValueObservingOptionInitial:注冊(cè)之后立刻調(diào)用接收方法灾票,如果配置了NSKeyValueObservingOptionNew峡谊,change參數(shù)內(nèi)容會(huì)包含新值,鍵為:NSKeyValueChangeNewKey刊苍;
NSKeyValueObservingOptionPrior:如果加入這個(gè)參數(shù)既们,接收方法會(huì)在變化前后分別調(diào)用一次,共兩次正什,可以分別獲取變化前后不通的值
context: 上下文啥纸,可以為kvo的回調(diào)方法傳值
observeValueForKeyPath: ofObject: change:context:
keyPath:屬性名稱,通過它可以獲取到當(dāng)前觀察的是哪個(gè)屬性
object:被觀察的對(duì)象
change:變化前后的值都存儲(chǔ)在change字典中
context:注冊(cè)觀察者時(shí)婴氮,context傳過來的值
線程
KVO 是線程同步的斯棒,發(fā)生變化的值與所觀察的值在同一個(gè)線程上盾致。
我們可以在改變被觀察對(duì)象的值時(shí),來開啟一個(gè)線程荣暮,在回調(diào)方法中查看是否同屬于同一個(gè)線程庭惜。
[self performSelectorInBackground:@selector(changeValue) withObject:nil];
- (void)changeValue {
self.starThreadLabel.text = [NSString stringWithFormat:@"開啟線程:%@", [NSThread currentThread]];
self.person.name = @"我是子線程"
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"當(dāng)前線程為:%@",[NSThread currentThread]);
}
PS: 以上都是一家之言,望各位看官多加實(shí)驗(yàn)來驗(yàn)證穗酥,非喜勿噴护赊。