KVO,即Key-Value Observing,是 Objective-C 對觀察者模式(Observer Pattern)的一種實現(xiàn)烤蜕。也是 Cocoa Binding 的基礎堕担。當被觀察對象的某個屬性發(fā)生更改時翻斟,觀察者對象會獲得通知吮廉。
KVO的通知方式一般分為兩種,一種是** 自動通知 ** 畸肆,另一種是** 手動通知 宦芦,系統(tǒng)默認的是 自動通知 **。
KVO的實現(xiàn)步驟
首先我們要有一個類轴脐,在這篇文章的例子中调卑,我們來對這個類的屬性進行觀察。
類的實現(xiàn)如下:
.h 文件
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *gender;
@end
.m 文件
#import "Person.h"
@implementation Person
@end
KVO的實現(xiàn)一般有以下三步
1. 添加觀察大咱,并指定被觀察的屬性
我們在程序啟動時為Person類的實例對象添加觀察者恬涧,即當前控制器。為了一會解釋手動實現(xiàn)碴巾,我們把name和gender屬性都設置為被觀察的屬性溯捆。且觀察的是這兩個屬性的新值。
Person *person = [[Person alloc] init];
[person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
[person addObserver:self forKeyPath:@"gender" options:NSKeyValueObservingOptionNew context:nil];
self.person = person;
2. 實現(xiàn)觀察方法
KVO觀察模式在被觀察屬性發(fā)生變化時厦瓢,會自動調(diào)用下面的方法提揍,我們要在方法里實現(xiàn)一些邏輯。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"new name = %@", [change objectForKey:NSKeyValueChangeNewKey]);
} else if ([keyPath isEqualToString:@"gender"]) {
NSLog(@"new gender = %@", [change objectForKey:NSKeyValueChangeNewKey]);
}
}
3. 移除觀察者
KVO會持有觀察者煮仇,所以在必要的時候劳跃,我們要移除觀察者。如果不移除浙垫,可能會造成訪問僵尸對象的情況刨仑,比如劉爽同學當時為一個單利變量添加了一個控制器觀察者,當這個控制器被pop掉后夹姥,單利中的值發(fā)生了改變杉武,結(jié)果在再次發(fā)出通知時,這個控制器變量不存在佃声,造成了訪問僵尸對象艺智,引起了奔潰。所以我們要移除觀察者圾亏。
移除觀察者一般是在觀察者的dealloc方法里完成的十拣。
- (void)dealloc
{
[self.person removeObserver:self forKeyPath:@"name"];
[self.person removeObserver:self forKeyPath:@"gender"];
}
自動通知的實現(xiàn)
現(xiàn)在封拧,我們就要進行被觀察屬性的修改了,看看自動通知是怎么工作的夭问。
person.name = @"Alan Walker";
person.gender = @"male";
person.name = @"DJ Earworm";
person.gender = @"male";
控制臺上打印
可見泽西,在KVO的自動通知中,只要是給被觀察對象賦值缰趋,無論該值是新值還是舊值捧杉,都會發(fā)出通知。
手動通知的實現(xiàn)
要實現(xiàn)手動通知秘血,我們需要對Person模型進行一些修改味抖。
在Person中添加** + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; ** 方法
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"name"]) {
return NO;
} else {
return [super automaticallyNotifiesObserversForKey:key];
}
}
這樣,我們就關閉了name屬性的自動通知灰粮。
1. 如果想要實現(xiàn)被觀察屬性在被賦的值與原來相同時仔涩,不進行通知,可以重寫name的setter方法
- (void)setName:(NSString *)name
{
if (self.name != name) {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
運行如下代碼
person.name = @"Alan Walker";
person.gender = @"male";
person.name = @"DJ Earworm";
person.gender = @"male";
person.name = @"DJ Earworm";
person.gender = @"male";
打印結(jié)果如下
2. 我們還可以按照自己的意愿讓通知啟動粘舟。
撤銷重寫setter方法熔脂,執(zhí)行如下代碼
person.name = @"Alan Walker";
person.gender = @"male";
[person willChangeValueForKey:@"name"];
person.name = @"DJ Earworm";
[person didChangeValueForKey:@"name"];
person.gender = @"male";
打印結(jié)果如下
由此可見,手動實現(xiàn)通知以下兩個方法比較重要柑肴。
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;