一、KVO
KVO的是KeyValue Observe的缩写,中文是键值观察。这是一个典型的观察者模式,利用它可以很容易使用实现视图组件和数据模型的分离,当数据模型的属性值改变之后作为监听器的视图组件就会被激发,激发时就会回调监听器自身,在Objc中实现KVO则必须实现NSKeyValueObServing协议,不过幸运的是NSObject已经实现了该协议,因此几乎所哟的NSObjectd对象都可以使用KVO。iOS中有个Notification的机制,也可以获得通知,但这个机制需要有个Center,相比之下KVO更加简洁而直接。
KVO的使用也很简单,就是简单的3步。
1.注册需要观察的对象的属性addObserver:forKeyPath:options:context:
2.实现observeValueForKeyPath:ofObject:change:context:方法,这个方法当观察的属性变化时会自动调用
3.取消注册观察removeObserver:forKeyPath:context:
这里有两种方式,一种是匹配keyPath,另一种是使用context
Demo:事列
A.注册需要观察的对象
- (void)viewDidLoad
{
[super viewDidLoad];
// 注册监听
[self.moveView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
self.p.name = @"小明";
[self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:PersonAgeContext];
}
B.实现
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// 方式1.匹配keypath
if ([keyPath isEqualToString:@"frame"]) {
NSLog(@"self.moveView.y = %f", self.moveView.y);
}
// 方式2.上下午
if (context == PersonAgeContext) {
NSLog(@"%@%d岁了", self.p.name, self.p.age);
}
}
C.移除监听
-(void)dealloc // ARC模式下
{
[self.moveView removeObserver:self forKeyPath:@"frame"];
[self.p removeObserver:self forKeyPath:@"age" context:PersonAgeContext];
}
KVO的 优势 :
1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
3.能够提供观察的属性的最新值以及先前值;
4.用key paths来观察属性,因此也可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点 :
1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
2.对属性重构将导致我们的观察代码不再可用;
3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
4.当释放观察者时不需要移除观察者。
二、KVC
1、KVC,即是指 NSKeyValueCoding,一个非正式的Protocol,提供一种机制来间接访问对象的属性。而不是通过调用Setter、Getter方法访问。KVO 就是基于 KVC 实现的关键技术之一。
KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说ObjC中几乎所有的对象都支持KVC操作,常用的KVC操作方法如下:
- 动态设置: setValue:属性值 forKey:属性名(用于简单路径)、setValue:属性值 forKeyPath:属性路径(用于复合路径,例如Person有一个Account类型的属性,那么person.account就是一个复合属性)
- 动态读取: valueForKey:属性名 、valueForKeyPath:属性名(用于复合路径)
Student.h
#import <Foundation/Foundation.h>
@class Teacher;
@interface Student : NSObject
{
NSString *_nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) Teacher *teacher;
@end
Student.m
#import "Student.h"
#import "Teacher.h"
@mplementation Student
- (instancetype)init
{
if (self = [super init])
{
_nickName = @"小样";
}
return self;
}
- (void)setTeacher:(Teacher *)teacher
{
_teacher = teacher;
//注册一个KVO
//第三个参数, NSKeyValueObservingOptionNew 获取最新改变后的值
//NSKeyValueObservingOptionOld 获取改变之前的值
//同时多个值都要传递 中间使用 |
[self.teacher addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
/*
0x01 | 0x02
0000 0001
0000 0010
0000 0011 0x03
*/
}
//#define KeyName @"name"
//extern NSString *const KeyName; 一般在头文件声明
//static NSString *const KeyName = @"name"; 在实现文件进行赋值
//实现监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
//由哪个kvo发过来的监听
if ([keyPath isEqualToString:@"name"])
{
//通过字典获取改变后的值
NSString *string = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"newKeyPath:%@", string);
}
}
- (void)dealloc
{
//注销监听
[self.teacher removeObserver:self forKeyPath:@"name"];
//ARC环境下重写dealloc,不要实现[super dealloc]
// [super dealloc];
}
Teacher.h
#import <Foundation/Foundation.h>
@interface Teacher : NSObject
@property (nonatomic, copy) NSString *name;
- (void)changeName;
@end
Teacher.m
#import "Teacher.h"
@implementation Teacher
- (void)changeName
{
_name = @"hahaha";
}
@end
main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *student = [[Student alloc] init];
student.name = @"Jobs";
//通过kvc的方式给变量赋值
[student setValue:@"Gates" forKey:@"name"];
//KVC key-value Coding 键值编码
NSLog(@"%@", [student valueForKey:@"name"]);
[student setValue:@"nickName" forKey:@"nickName"];
NSLog(@"%@", [student valueForKey:@"nickName"]);
NSArray *array = @[student];
//OC 动态绑定的特性
for (id obj in array)
{
NSLog(@"%@", [obj valueForKey:@"name"]);
}
//KVO key-value observer 键值观察者
/*
1.注册一个kvo监听
2.实现监听方法
3.注销kvo监听
MVC 可读性,可维护性,可移植性
model 数据模型
controller 控制器
view 视图
*/
//!!只有通过setter方法进行修改的时候,才能使用kvo的监听!!
Teacher *teacher = [[Teacher alloc] init];
student.teacher = teacher;
teacher.name = @"teacher1";
teacher.name = @"teacher2";
[teacher changeName];
NSLog(@"%@", teacher.name);
}
return 0;
}
打印结果:
在Student类中有一个_nickName的属性,但是没有提供任何getter/setter的访问方法。同时在main.m里面有一个Student的对象指针。
当Student实例化后,常规来说是无法访问这个对象的_nickName属性的,不过通过KVC我们做到了,在打印里有输出信息,这就说明确实读写了_nickName属性
KVC的常用方法:
- (id)valueForKey:(NSString *)key; -(void)setValue:(id)value forKey:(NSString *)key;
valueForKey的方法根据key的值读取对象的属性,setValue:forKey:是根据key的值来写对象的属性。
注意:
(1). key的值必须正确,如果拼写错误,会出现异常
(2). 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来
(3). 因为类key反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去
(4). NSArray/NSSet等都支持KVC