iOS开发之KVO

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方法中移除观察者,否则会造成内存泄漏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值