KVO

添加观察方法:

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

第一个参数是观察者对象,负责处理监听事件;第二个是观察的属性的路径;第三个是观察的选项;第四个是上下文。

监听回调方法:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

大部分的参数是和添加方法里的对应的,不同的是观察选项和change参数,但两者是对应的。

options有四个选项

NSKeyValueObservingOptionNew:change会接收到观察属性的新值;

NSKeyValueObservingOptionOld:change会接收到观察属性的旧值;

NSKeyValueObservingOptionInitial:回调方法会在观察属性初始化的时候调用,但不会接收到这个初始值,除非和NSKeyValueObservingOptionNew选项一起使用;

NSKeyValueObservingOptionPrior:回调方法会触发两次,一次是观察属性改变前,一次是改变后,所以可以配合willChangeValueForKey:方法一起使用


比如说大家都喜欢给宠物取名字,养的猫也会有名字

@interface Cat : NSObject

@property (nonatomic, strong) NSString* name;

@end

我们想要在猫的名字改变的时候有个通知,我们在一个viewcontroller里面做测试,其他多余的代码暂时不要

#import "KVOViewController.h"
#import "Cat.h"

@interface KVOViewController ()

@property (nonatomicstrong) Cat* whiteCat;

@end

@implementation KVOViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _whiteCat = [[Cat alloc] init];
    _whiteCat.name = @"hello";
    
    [_whiteCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    
}

- (IBAction)observerTap:(id)sender {
    _whiteCat.name = @"kitty";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@"white cat's name change");
    }
    
}

@end

我们在当前进行观察的,所以第一个参数观察者是self,当然这不是固定的,可以指到其他地方观察,当都要实现监听方式。

我们是要在猫的名字改变的时候得到事件,所以监听的第二个参数keypath是name。

keypath描述的是要观察的属性,而这个属性,要符合KVC协议的,简单点说,就是这个属性要能够被进行以下操作

(void)setValue:(id)value forKey:(NSString *)key;

就是说这个属性能够被键值编码,如果我们想观察一个数组的count属性,直接使用

[_obArr addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

程序会crash的,因为数组的count属性不符合键值编码,即简单的不能被setValue:forKey,因为它不是id类型。

keypath,顾名思义,这是一个寻找的路径,所以这里如果是想观察name的属性也是可以用点语法观察的,当然,前提也是要符合KVO协议。

最后一个参数,一般是传nil或者NULL,但作用呢,其实很强大,比如当前有两个Cat的实例,我都要观察他们的name属性变化,我该如何区分是哪个实例呢?就用这个参数,context。我们给上面的例子添加点代码:

@interface KVOViewController ()

@property (nonatomicstrong) Cat* whiteCat;
@property (nonatomicstrong) Cat* blackCat;

@end

@implementation KVOViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    _whiteCat = [[Cat alloc] init];
    _whiteCat.name = @"hello";
    
    [_whiteCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"White"];
    
    _blackCat = [[Cat alloc] init];
    _blackCat.name = @"kitty";
    
    [_blackCat addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"Black"];
}

- (IBAction)observerTap:(id)sender {
    _whiteCat.name = @"kitty";
    _blackCat.name = @"hello";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if ([keyPath isEqualToString:@"name"]) {
        
        id instanceObj = (__bridge id)(context);
        NSString* contextStr = instanceObj;
        if ([contextStr isEqualToString:@"White"]) {
            NSLog(@"white cat's name change");
        } else if ([contextStr isEqualToString:@"Black"]) {
            NSLog(@"black cat's name change");
        }
        
    }
    
}

@end



如果是自己观察自己的属性变化呢,该如何做?下面是我的实验代码:

@interface Cat : NSObject

@property (nonatomicstrongNSString* name;

- (void)beginToObserver;
- (void)changeName;

@end

@implementation Cat

- (void)beginToObserver {
    [self addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}

- (void)changeName {
    _name = @"hello";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"here");
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@"cat's name change");
    }
    
}

@end

生成了Cat实例后,调用beginToObserver添加观察,在某处触发改变调用changeName改变名字。

运行的结果是,触发了按钮事件,观察的字符串的值也改变了,但是就是不触发监听回调方法。为什么?

我们再修改一处代码,把字符串的再次赋值方式,变成点语法赋值:

- (void)changeName {
    self.name = @"hello";
}

这次运行就触发了监听回调方法了。

添加观察前后,isa指针指向发生了改变,这是在KVO通过runtime创建被观察的class的subclass(通常会以NSKVONotifying前缀),在这个subclass里,set方法会被重写,在set方法里实现了通知机制,所以调用点语法才能触发通知。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值