关于KVC
KVC是什么?
Key-Value Coding,即键值编码。它是一种不通过存取方法,而通过属性名称字符串间接访问属性的机制。
KVC常用的方法
前两个方法无论获取值还是赋值,只需要传入属性名称的字符串就行了。但KVC也提供了传入path
的方法。所谓path,就是用点号连接的多层级的属性,比如student.name
,student属性里的name属性。
- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
KVC对多种数据类型的支持
首先要说的是对于基本数据类型的属性,KVC的这几个方法会自动装箱和拆箱。
其次,KVC也支持数组和字典等集合数据。这里不多探讨,关于这些知识可以看这篇博文:KVC/KVO原理详解及编程指南
KVC的原理:KVC是怎么访问属性的?
关于KVO
KVO是什么?
Key-Value Obersver,即键值观察。它是观察者模式的一种衍生。基本思想是,对目标对象的某属性添加观察,当该属性发生变化时,会自动的通知观察者。这里所谓的通知是触发观察者对象实现的KVO的接口方法。
** KVO是解决model和view同步的好法子。**
另外,KVO的优点是当被观察的属性值改变时是会自动发送通知的,这比通知中心需要post通知来说,简单了许多。
KVO怎么用?
1.首先给目标对象的属性添加观察:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2.实现下面方法来接收通知,需要注意各个参数的含义:
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;
3.最后要移除观察者:
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
举一个?:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
{
Student *_student;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_student = [[Student alloc] init];
_student.stuName = @"oldName_hu";
// 1.给student对象的添加观察者,观察其stuName属性
[_student addObserver:self forKeyPath:@"stuName" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// 此时,stuName发生了变化
_student.stuName = @"newName_wang";
}
// stuName发生变化后,观察者(self)立马得到通知。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
// 最好判断目标对象object和属性路径keyPath
if(object == _student && [keyPath isEqualToString:@"stuName"])
{
NSLog(@"----old:%@----new:%@",change[@"old"],change[@"new"]);
}else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)dealloc
{
// 移除观察者
[_student removeObserver:self forKeyPath:@"stuName"];
}
@end
上面代码需要注意的一点是接收通知的方法里最好判断一下目标对象和属性路径,因为有可能有多个KVO观察多个对象的属性;而且,父类也有可能使用了KVO哦,所以在else里,对现有条件外的情况交给父类去处理。
KVO的原理:KVO是怎么实现的?
重新的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
引用:
详解键值观察(KVO)及其实现机理
KVC/KVO原理详解及编程指南