KVO

43次阅读

共计 3057 个字符,预计需要花费 8 分钟才能阅读完成。

原文链接
Key-Value Observing 键值观察,是一种设计模式观察者模式的实现
官方定义
键值观察提供了一种机制,允许对象通知其他对象的特定属性的更改。它对应用程序中模型和控制器层之间的通信特别有用。(在 OS X 中,控制器层绑定技术严重依赖于键值观察。)控制器对象通常观察模型对象的属性,视图对象通过控制器观察模型对象的属性。然而,另外,模型对象可以观察其他模型对象(通常用于确定从属值何时改变)或甚至自身(再次确定从属值何时改变)。
先看下使用 KVO 的姿势
Xcode -> New -> MacOS -> CommandLine 新建工程,创建 Person 类
Person.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic ,copy) NSString *name;

@property (nonatomic ,assign) NSUInteger age;

@property (nonatomic ,copy) NSArray<Person *> *friends;

@end

NS_ASSUME_NONNULL_END

Person.m
#import “Person.h”

@implementation Person

– (instancetype)init {

self = [super init];

if (self) {
_name = @””;
_age = 0;
_friends = @[];
}

return self;
}

– (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

// 当收到通知的时候打印观察的对象,旧值和新值
NSLog(@”\nReceving ObserveValueChanged \nObject: %@ OldValue: %@, NewValue: %@”,object, change[NSKeyValueChangeOldKey], change[NSKeyValueChangeNewKey]);
change[@”new”]);
}

// 重写以便打印对象的属性
– (NSString *)description {

return [NSString stringWithFormat:@”- name: %@, age: %ld, friends: %@”,self.name, self.age, self.friends];
}

@end

打开 main.m,创建 Alice 和 Bob,设置 Bob 观察 Alice 的 age 属性
Person *Alice = Person.new;
Alice.name = @”Alice”;
Alice.age = 18;

Person *Bob = Person.new;
Bob.name = @”Bob”;
Bob.age = 28;

[Alice addObserver:Bob forKeyPath:@”age” options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:PersonChangeContext];

Alice.age = 100;

[Alice removeObserver:Bob forKeyPath:@”age”];

Alice.age = 200;

可以看到控制台输出了一次 Alice 的 age 属性的前后变化.
其中,NSKeyValueObservingOptions 有以下几个选项,可以使用 | 符号组合使用

NSKeyValueObservingOptionNew // 收到通知时 change 字典将包含新值
NSKeyValueObservingOptionOld // 收到通知时 change 字典将包含旧值
NSKeyValueObservingOptionInitial // 在 addObserver 时会发送通知,change 字典将包含初始值
NSKeyValueObservingOptionPrior // 在所观察 keyPath 改变之前将收到通知

change 字典的 key 值在这里
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey

KVO 的使用场景有很多,比如 Person 拥有一个 account 属性,person 需要获取 account 的变化该如何处理呢,轮训或许是个办法,但是无疑效率低下,而且很不安全,更合理的办法就是使用 KVO 观察 account,在 account 发生变化时更新。
原理

现在我们已经知其然了,但是还不知其所以然。
先说结论

系统通过 runtime 生成继承与被观察者的新类 (NSKVONotifying_Person),对原对象进行 isa-swizzing(isa 混写)
根据 KVC(键值编码) 对对象的 keypath 进行 hock
在将要对属性进行赋值操作时发送通知

如何证明上述结论呢,上代码!
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import “Person.h”

static void *PersonChangeContext = &PersonChangeContext;

int main(int argc, const char * argv[]) {
@autoreleasepool {

Person *Alice = Person.new;
Alice.name = @”Alice”;
Alice.age = 18;

Person *Bob = Person.new;
Bob.name = @”Bob”;
Bob.age = 28;

Class cls0 = object_getClass(Alice);

[Alice addObserver:Bob forKeyPath:@”age” options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:PersonChangeContext];

Alice.age = 100;

Class cls1 = object_getClass(Alice);

NSLog(@”%@ %@”,cls0,cls1);

}
return 0;
}

首先引入 runtime,这样我们可以打印 isa 所指向的真实的类,
在向 Alice 添加观察者之前,先获取 Class
在向 Alice 添加观察者之后,获取 Class

Person NSKVONotifying_Person

可以看到输出确实如此
这就是 KVO 的核心思路了,关于 KVC 请看这里.

正文完
 0