KVC是怎么访问属性的?KVO怎么实现的?
2017-07-20 本文已影响69人
test158888
KVC是怎么访问属性的
- KVC在某种程度上提供了替代存取方法(访问器方法)的方案,不过存取方法终究是个好东西,以至于只要有可能,KVC也尽可能先尝试使用存取方法访问属性。当使用KVC访问属性时,它内部其实做了很多事:
- 首先查找有无<property>,set<property>,is<property>等property属性对应的存取方法,若有,则直接使用这些方法;
- 若无,则继续查找<property>,_get<property>,_set<property>等方法,若有就使用;
- 若查询不到以上任何存取方法,则尝试直接访问实例变量<property>,<property>;
- 若连该成员变量也访问不到,则会在下面方法中抛出异常。之所以提供这两个方法,valueForUndefinedKey:和setValue:forUndefinedKey:方法,,就是让你在因访问不到该属性而程序即将崩掉前,供你重写,在内做些处理,防止程序直接崩掉。
KVO是什么?
-
Key-Value Obersver,即键值观察。它是观察者模式的一种衍生。基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,会自动的通知观察者。这里所谓的通知是触发观察者对象实现的KVO的接口方法。KVO是解决model和view同步的好法子。另外,KVO的优点是当被观察的属性值改变时是会自动发送通知的,这比通知中心需要post通知来说,简单了许多。
-
首先给目标对象的属性添加观察:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- 实现下面方法来接收通知,需要注意各个参数的含义:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
- 最后要移除观察者:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
KVO是怎么实现的?
当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中被观察属性的 setter 方法,在setter方法里使其具有通知机制。因此,要想KVO生效,必须直接或间接的通过setter方法访问属性(KVC的setValue就是间接方式)。直接访问成员变量KVO是不生效的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
重新的setter方法里到底干了什么,而使其就有了通知机制呢?其实只是在setter方法里,给属性赋值的前后分别调用了两个方法
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
而- (void)didChangeValueForKey:(NSString *)key;会调用
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
这就是KVO实现的基本原理了!
当没有存取方法而通过KVC的setValue修改属性值时,同样的在运行时也会在setValue:forKey方法里默认调用上面俩方法。
其实我们也可以手动,显式的调用这两个方法,以使其具有通知机制。
下面用例子验证:
#import "ViewController.h"
@interface ViewController ()
{
NSString *_testStr;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 给self的添加self观察者,自己观察自己的testStr成员变量
[self addObserver:self forKeyPath:@"testStr" options:NSKeyValueObservingOptionNew context:nil];
[self willChangeValueForKey:@"testStr"];
_testStr = @"this is a test"; // 直接修改成员变量的值,但是显式的、手动的调用上下俩方法,使其就有通知机制
[self didChangeValueForKey:@"testStr"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
if(object == self && [keyPath isEqualToString:@"testStr"])
{
NSLog(@"----new:%@----",change[@"new"]);
}else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
// 移除观察者
[self removeObserver:self forKeyPath:@"stuName"];
}
@end