KVO初探

作者: SPIREJ | 来源:发表于2019-11-12 19:29 被阅读0次

最好的学习文档是 KVO苹果官方文档

KVO三步曲

  1. 添加观察者
- (void)addObserver:(NSObject *)observer
         forKeyPath:(NSString *)keyPath 
            options:(NSKeyValueObservingOptions)options
            context:(nullable void *)context;
  • observer:注册KVO通知的对象。观察者必须实现key-value observing方法observeValueForKeyPath:ofObject:change:context:
  • keyPath:观察者的属性的keyPath,相对于接受者,值不能为nil
  • options:``NSKeyValueObservingOptions的组合,它指定了观察通知中包含了什么,可以查看“NSKeyValueObservingOptions”
  • context: 标签,区分多监听或继承情况
  1. 响应观察
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
                      ofObject:(nullable id)object
                        change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
                       context:(nullable void *)context;
  1. 移除观察
- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath;

KVO使用

  • 示例一个SPPerson类
@interface SPPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nick;
@property (nonatomic, copy) NSString *downloadProgress;
@property (nonatomic, assign) double writtenData;
@property (nonatomic, assign) double totalData;
@property (nonatomic, strong) NSMutableArray *dataArray;

@end
  • SPStudent继承自SPPerson
@interface SPStudent : SPPerson<NSCopying>
// 什么也没做
@end
  • 注册观察者
static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;

// 注册观察者
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self.person addObserver:self forKeyPath:@"age" options:(NSKeyValueObservingOptionNew) context:NULL];
[self.student addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:StudentNameContext];

[self.person addObserver:self forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew context:NULL];
  • 响应观察者回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    NSLog(@"%@ -- %@", change, object);
}
  • 发送新值 setter方法
self.person.name = @"spirej";
self.student.name = @"sp_spirej";

// 集合,取值和赋值过程和普通类型不一样    
[[self.person mutableArrayValueForKey:@"dataArray"] addObject:@"joo"];
  • 移除观察者
- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"name" context:PersonNameContext];
    [self.person removeObserver:self forKeyPath:@"age"];
    [self.student removeObserver:self forKeyPath:@"name" context:StudentNameContext];
    [self.person removeObserver:self forKeyPath:@"dataArray"];
}

打印结果:

2019-11-12 18:54:30.748597+0800 KVO[27143:3715662] {
    kind = 1;
    new = spirej;
} -- <SPPerson: 0x6000008a65c0>
2019-11-12 18:54:30.749288+0800 KVO[27143:3715662] {
    kind = 1;
    new = "sp_spirej";
} -- <SPStudent: 0x6000008a6340>
2019-11-12 18:54:30.752466+0800 KVO[27143:3715662] {
    indexes = "<_NSCachedIndexSet: 0x600001dd7280>[number of indexes: 1 (in 1 ranges), indexes: (1)]";
    kind = 2;
    new =     (
        joo
    );
} -- <SPPerson: 0x6000008a65c0>

KVO自动观察和手动观察

  1. 自动观察
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    return YES;
}

默认返回YES开启自动观察,亦可根据key值调整

  1. 手动观察
- (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}

在setter方法属性赋值前后调用willChangeValueForKey 和 didChangeValueForKey 即可开启手动观察,注意:如果自动观察没有关闭,则会观察两次

KVO 的 context

注册观察者方法和响应回调方法中的context,是标签作用,可用来区分同一个属性多监听的情况或继承关系中观察同一个属性的情况

static void *PersonNameContext = &PersonNameContext;
static void *StudentNameContext = &StudentNameContext;


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    if (context == PersonNameContext) {
        // ...
    }else if (context == StudentNameContext) {
        // ...
    }else if (context == PersonNickContext) {
        // ...
    }
    
    NSLog(@"%@ -- %@", change, object);
}

KVO路径集合

当观察对象受多个因素影响时,可以keyPathsForValuesAffectingValueForKey方法把多个因素捆绑起来

例如我这里要观察下载进度,又假如下载进度受已下载数据和总数据影响,那么就可以把已下载进度和总进度捆绑为观察路劲集合

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
    
    NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"downloadProgress"]) {
        NSArray *affectingKeys = @[@"totalData", @"writtenData"];
        keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
    }
    return keyPaths;
}

使用观察:

[self.person addObserver:self forKeyPath:@"downloadProgress" options:(NSKeyValueObservingOptionNew) context:NULL];

示例代码:
https://github.com/SPIREJ/KVO

相关文章

  • KVO学习笔记

    1.KVO初探学习2.KVO 底层原理探索 1.KVO初探学习 移除观察者的重要性 (IOS11之后说不移除是不对...

  • iOS-KVO相关

    KVO相关 一、KVO初探 — 响应观察 (一)KVO 使用 的 三部曲 1、添加观察 2、响应 3、析构 (...

  • KVO初探

    最好的学习文档是 KVO苹果官方文档 KVO三步曲 添加观察者 observer:注册KVO通知的对象。观察者必须...

  • ReactiveCocoa相关

    随手记录: 1、iOS开发系列--Objective-C之KVC、KVO 2、ReactiveCocoa初探

  • 03--KVC/KVO本质05--KVO 本质

    [TOC] (一)KVO 初探 1. 基本用法 添加观察 监听观察 移除观察 通知使用完之后,一定要移除,否则会有...

  • KVC分析

    KVO初探 根据官方文档我们来验证一下 set方法原文 1、两个方法要是同时存在会先找找setName方法,要是没...

  • KVO的原理初探及应用

    1. 实现原理 关于KVO的实现原理,苹果有如下说明: Key-Value Observing Implement...

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • 04. KVO使用,原理,本质

    问题 KVO日常使用 KVO原理(KVO本质是什么) 如何手动触发KVO 直接修改成员变量会触发KVO吗 KVO图...

  • 苹果 ARKit 初探

    苹果 ARKit 初探 苹果 ARKit 初探

网友评论

      本文标题:KVO初探

      本文链接:https://www.haomeiwen.com/subject/furtictx.html