KVO:key-value-observing,键值监听。
KVO的使用被叫做观察者设计模式,这种设计模式提供了一种机制:指定一个对象的属性,当被观察的对象属性发生变化的时候,就会发送通知作出也相应的处理。
这里需要注意一点:被观察的对象属性必须实现了setter、getter方法或者使用KVC存取。
KVO在实际开发中的使用:
1、监听某个对象属性,当属性发生变化时,实现一些操作。
2、实现model和view之间的通信。
KVO色基本原理:
当观察一个对象A时,KVO机制动态创建一个A对象当前类的子类,并为这个新的子类重写被观察属性的setter方法,setter方法随后负责通知观察对象属性值得改变状况。
KVO的使用:KVO的使用比较简单,只有3步
1、给被观察的对象属性添加观察者,调用方法:
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid *)context;
2、实现回调方法,在回调方法中做需要的操作,实现回调方法:- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
3、移除观察者,调用方法:- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
下面,写一个简单的示例:有一个Studen类,类中有一个name属性,监听name属性,当name发生变化的时候,打印一下。
示例完整代码如下:
Student类代码:
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic,copy)NSString *name;
@end
ViewController中代码:
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
{
Student *student;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
//给对象属性添加观察者方法
[selfuseKVOFaction];
//创建视图方法
[selfsetUI];
}
- (void)setUI{
UILabel *label = [[UILabelalloc]initWithFrame:CGRectMake(100, 300, 200, 30)];
label.backgroundColor = [UIColoryellowColor];
label.tag = 1000;
label.text =student.name;
[self.viewaddSubview:label];
UIButton *button = [UIButtonbuttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColorblackColor];
button.frame =CGRectMake(100, 200, 200, 30);
[button setTitle:@"修改name的值"forState:UIControlStateNormal];
[button addTarget:self action:@selector(changeValue:)forControlEvents:UIControlEventTouchUpInside];
[self.viewaddSubview:button];
}
- (void)useKVOFaction{
//创建一个Student对象
student = [[Studentalloc]init];
//给对象属性赋值
student.name =@"名字";
//为student对象的name属性添加观察者
[studentaddObserver:selfforKeyPath:@"name"options:NSKeyValueObservingOptionNewcontext:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//当student对象的name属性发生变化的时候会在视图上显示新的名字
UILabel *label = [self.viewviewWithTag:1000];
label.text =student.name;
}
- (void)changeValue:(UIButton *)btn{
//使用KVC给student对象的name属性赋值
[studentsetValue:@"新名字"forKey:@"name"];
//操作完成后,移除监听
[studentremoveObserver:selfforKeyPath:@"name"];
}
@end
代码运行结果:
点击按钮前: 点击按钮后:
在上面的示例中
//为student对象的name属性添加观察者
[student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
给student的name属性添加方法,student是被观察的对象,name是被观察的对象属性。addObserver: 后跟的参数书观察者,也就是说是谁要观察监听student这个对象的name属性,这里的self表示当前的控制器是观察者。
forKeyPath:@"name"后面的参数就是对象的属性,这里的字符串name。
options:后面的参数:
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04,
NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08
};
NSKeyValueObservingOptionNew:提供更改前的值NSKeyValueObservingOptionOld:提供更改后的值
NSKeyValueObservingOptionInitial:观察最初的值,在注册观察者的时候会触发一次回调方法
NSKeyValueObservingOptionPrior:分别在值修改前后出触发方法,一次修改有两次回调方法的调用。
context:后跟的参数是要传递的东西,可以是任何类型,在使用的时候注意类型转化,有了这个参数的传入就可以实现一些通信,从而可以实现一些类之间的解耦。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//当student对象的name属性发生变化的时候会在视图上显示新的名字
UILabel *label = [self.view viewWithTag:1000];
label.text = student.name;
}
这个回调方法,是要处理的操作,在被监听的对象属性发生变化的时候,这个回调方法就被调用,触发调用的次数根据options的参数决定。上面的代码中,是在按钮的点击事件里移除的监听。而且是在修改了name的值以后。
- (void)changeValue:(UIButton *)btn{
//使用KVC给student对象的name属性赋值
[student setValue:@"新名字" forKey:@"name"];
//操作完成后,移除监听
[student removeObserver:self forKeyPath:@"name"];
}
观察者的移除一定要注意,要在合适的地方移除监听。当监听被移除以后,这个监听就没有了,所以当再次点击按钮的时候[student removeObserver:self forKeyPath:@"name"];
这段代码还是会被执行,但是已经没有监听了,这样就会造成系统崩溃。所以,在上面的代码中,在按钮的点击事件中移除监听就不是一个合适的地方。那么什么才是合适的地方呢,在开发中,最长用到的地方时在ViewController的声明周期的回调方法中去移除,而且是在视图将要消失或者视图已经消失的方法中去移除监听。将上面代码做简单修改:
将
[student removeObserver:self forKeyPath:@"name"];
方法,放在ViewController的回调方法viewDidDisappear中
- (void)viewDidDisappear:(BOOL)animated{
[selfviewDidDisappear:YES];
//操作完成后,移除监听
[studentremoveObserver:selfforKeyPath:@"name"];
}
这样就不会有系统崩溃的问题了。
KVO作为一种常用的设计模式,很简单,只有一个简单的回调就可以实现想要的操作,而且提供了观察新值、旧值和初始值的参数。但是由于KVO的实现原理的原因,创建子类,重写setter方法等都是相对消耗内存的,所以,在使用的时候还是要根据实际情况合理使用。