iOS开发之KVO
KVO简介:
KVO(Key - Value Observing)键值观察,是一种观察者模式的应用。当观察者将被观察者的某个属性设置为观察的对象时,若被观察的该属性值发生变化时,就会触发观察者对象所实现的KVO接口方法,从而达到通知观察者的目的。KVO的定义也是在NSObject的扩展类(NSKeyValueObserving)中实现的,所以只有继承了NSObject的类才可以使用KVO,一些纯Swift的类或者结构体是无法使用KVO的,NSArray,NSSet也可以使用KVO(只能监测到数组本身的变化,但是无法检测到数组内容的变化);
实现原理 isa - swizzing(isa指针交换):
当一个类A被作为被观察者对象时,编译器会在编译时给A生成一个派生类(NSKVONotifying_A)A1,并将A的isa指针指向这个派生类A1。当另一个类的对象B注册为A对象属性Ap的观察者时,派生类A1就会重写属性Ap的set方法,并在set方法中添加通知的代码。由于OC在发送消息的时候会通过isa指针找到对象所属的类,所以在发送消息时就会找到派生类对象A1并将消息发给A1,当调用到被监测属性Ap的set方法时,就会将通知消息发送给观察者对象B了。所以KVO的实现得益于Runtime强大的动态能力。
注意:由于KVO通知是通过重写属性的setter方法实现的,所以必须通过属性的setter方法来改变属性值时KVO通知才会被触发,如果通过属性的"_"成员变量来修改属性值时是不会触发KVO的。
主要方法:
- - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
注册观察者 observer:观察者对象; KeyPath:要监测的键路径;options:需要监测的选项;context:上下文用来区分消息;
NSKeyValueObservingOptionNew:change字典包括改变后的值
NSKeyValueObservingOptionOld:change字典包括改变前的值
NSKeyValueObservingOptionInitial:注册后立刻触发KVO通知
NSKeyValueObservingOptionPrior:值改变前后各发一次通知
- - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
移除观察者 observer:观察者对象; KeyPath:监测的键路径;
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change
context:(void *)context
监听回调方法
一般键值监听方法:
- Cameras被监听对象,被监听属性为nicon1
//Cameras.h
#import <Foundation/Foundation.h>
#import "NiCon.h"
NS_ASSUME_NONNULL_BEGIN
@interface Cameras : NSObject
@property(nonatomic,strong)NiCon * nicon1;
@property(nonatomic,copy)NSString * devInfo;
@property(nonatomic,strong)NSMutableArray * numArr;
-(void)setPropertyWithNicon:(NiCon*)nicon;
-(instancetype)initWithNicon:(NiCon*)nicon;
@end
NS_ASSUME_NONNULL_END
- 监听者对象NiCon实现监听回调方法:
//NiCon.m
#import "NiCon.h"
@implementation NiCon
//KVO监听方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"<====变了变了!!! keyPath: %@ object: %@ \nchange: %@ \ncontext: %@", keyPath, object, change, context);
}
@end
- viewController中调用添加监听者,修改属性:
//ViewController.m
#import "Cameras.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NiCon * nicon = [[NiCon alloc]init];
Cameras * cam = [[Cameras alloc]initWithNicon:nicon];
[cam addObserver:nicon forKeyPath:@"nicon1" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"cam.nicon1"];
//使用KVC和set方法都可以触发KVO
cam.nicon1 = nicon;
[cam setValue:nicon forKey:@"nicon1"];
//修改成员变量则无法触发KVO
[cam setPropertyWithNicon:nicon];
}
@end
自定义控制KVO监听
- 首先手动实现该属性的setter方法,在设值前后,分别调用willChangeValueForKey: 和didChangeValueForKey方法,来手动通知系统。该key的值发生了变化。
//Cameras.m
//自定义KVO:
-(void)setNicon1:(NiCon *)nicon1{
[self willChangeValueForKey:@"nicon1"];//通知系统该值即将变化
_nicon1 = nicon1;
[self didChangeValueForKey:@"nicon1"];//通知系统该值已经发生变化
}
- 重写automaticallyNotifiesObserversForKey:方法将该key的系统自动通知关闭。(如果要禁掉该类的KVO则直接返回NO);
//Cameras.m
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
if ([key isEqualToString:@"nicon1"]) {
return NO;//对该key禁用系统自动通知,若要直接禁用该类的KVO则直接返回NO;
}
return [super automaticallyNotifiesObserversForKey:key];//对于其他属性使用系统自动通知
}
- 然后在viewController中添加进监听者,监听者实现监听方法即可监听属性的变化,从而实现KVO的选择性开放。
KVO键值观察之依赖键:
当被观察的某个键值是由其他对象的某个或多个键值所决定的时候,就可以使用依赖键了,当可引起被观察键键值变化的任何一个键值发生变化时,都会触发KVO的方法。
实现如下:
- 首先重写被观察的依赖键的get方法(这里依赖键为devInfo),返回依赖键的构成(依赖键与其他键值的关系),这里devInfo属性值由属性对象nicon1.devName 和nicon1.devNO构成;
//Cameras.m
-(NSString *)devInfo{
return [NSString stringWithFormat:@"%@ # %d", _nicon1.devName, _nicon1.devNO];
}
- 重写被观察者对象(Cameras)的+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key方法,判断返回的键名为依赖键时为在返回的Set集合中添加被依赖的所有键名。
//Cameras.m
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
NSSet * set = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"devInfo"]) {
set = [NSSet setWithObjects:@"nicon1.devName",@"nicon1.devNO", nil];
NSLog(@"====> set: %@", set);
}
return set;
}
- 在观察对象方法中实现观察者监听的回调方法;
//NiCon.m
#import "NiCon.h"
@implementation NiCon
//KVO监听方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"<====变了变了!!! keyPath: %@ object: %@ \nchange: %@ \ncontext: %@", keyPath, object, change, context);
}
@end
- 添加观察者,便可实现监听依赖键;
//viewController.m
NiCon * nicon = [[NiCon alloc]init];
Cameras * cam = [[Cameras alloc]initWithNicon:nicon];
[cam addObserver:nicon forKeyPath:@"devInfo" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"cam.devInfo"];
nicon.devName = @"索尼X88";
nicon.devNO = 1100;
KVO是在线程的同步的环境下使用的,如果在多个线程中异步改变被监测的键值时会造成程序崩溃,多个线程中同步更改可以正确触发KVO;另外在监听完成之后必须在相应的对象dealloc方法中移除观察者,否则会造成内存泄漏。