KVO拾遗

作者: lmfei | 来源:发表于2019-12-30 22:56 被阅读0次

KVO定义

KVO全称Key Value Observing,根据runtime实现的,是观察者设计模式的一种实现,由NSKeyValueObserving协议提供支持,NSObject类实现了该协议,因此所有NSObject的子类都可以使用KVO

KVO使用

使用三部曲:
  1. 注册监听
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

observer:观察者
keyPath:观察的属性路径
options:观察值的类型 NSKeyValueObservingOptionNew-新值 NSKeyValueObservingOptionOld-老值
content:上下文,快速定位观察属性

  1. 实现监听代理
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;

keyPath:观察的属性路径
content:上下文
change中包括的值
kind:
//set方法
NSKeyValueChangeSetting = 1,
//插入
NSKeyValueChangeInsertion = 2,
//删除
NSKeyValueChangeRemoval = 3,
//替换
NSKeyValueChangeReplacement = 4,复制代码
new:新值 观察属性时options设置了NSKeyValueObservingOptionNew,会返回
old:旧值 观察属性时options设置了NSKeyValueObservingOptionOld,会返回

  1. 移除监听
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

注:一定要移除,不然会出现莫名的崩溃问题

手动观察\自动观察

重写

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key;

默认返回YES,返回NO则不自动观察属性,需要观察属性需要在对应setter方法设置值的前后,执行

- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

例:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return YES;
}
- (void)setName:(NSString *)name {
    //手动添加观察者
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}
多属性决定一个属性的值

实现

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

例:

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

- (CGFloat)proportion {
    return self.width/self.height;
}
集合属性的监听

例:

//LAnimal.h
@property (nonatomic, strong) NSMutableArray *actions;

//ViewController.h
@property (nonatomic, retain) LAnimal *animal;

//ViewController.m/viewDidLoad
    [self.animal addObserver:self forKeyPath:@"actions" options:(NSKeyValueObservingOptionNew) context:NULL];

//使用此方法集合属性的无法监听
[self.animal.actions addObject:@"fly"];

//需要使用KVC 集合的setter方法
[[self.animal mutableArrayValueForKey:@"actions"] addObject:@"run"];//kind:2
[[self.animal mutableArrayValueForKey:@"actions"] removeLastObject];//kind:3
[self.animal mutableArrayValueForKey:@"actions"][0] = @"eat";//kind:4

KVO原理探索

  1. KVO观察的是setter方法

通过监听实例变量与属性

//LAnimal.h
{
    @public
    NSString *secondName;
}
@property (nonatomic, copy) NSString *name;

//ViewController.m/viewDidLoad
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
[self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];

//然后设置值
self.animal.name = @"Lucy";
self.animal->secondName = @"haha";

通过上面代码,可以发现实例变量并没有被KVO监听,而属性是被监听的,推断KVO监听的处理可能是在setter方法

  1. 在被监听后,类前后的变化
//ViewController.m
- (void)catClasses: (Class)cls {
    int count = objc_getClassList(NULL, 0);
    NSMutableArray *tmpAry = [NSMutableArray arrayWithObject:cls];
    Class *clses = (Class *)malloc(sizeof(Class)*count);
    objc_getClassList(clses, count);
    for (int i=0; i < count; i++) {
        if (cls == class_getSuperclass(clses[i])) {
            [tmpAry addObject:clses[i]];
        }
    }
    free(clses);
    NSLog(@"%s\n%@",__FUNCTION__, tmpAry);
}

//ViewController.m/viewDidLoad
[self catClasses:[LAnimal class]];
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
[self.animal addObserver:self forKeyPath:@"secondName" options:NSKeyValueObservingOptionNew context:@"second_name"];
NSLog(@"************************");
[self catClasses:[LAnimal class]];

打印信息:

2019-12-25 16:53:25.960926+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
(
    LAnimal
)
2019-12-25 16:53:32.088372+0800 KVODemo[38333:4314339] ************************
2019-12-25 16:53:32.093974+0800 KVODemo[38333:4314339] -[ViewController catClasses:]
(
    LAnimal,
    "NSKVONotifying_LAnimal"
)

通过打印信息可以知道属性被监听后,会重新生成LAnimal的一个子类NSKVONotifying_LAnimal

  1. 查看生成的子类哪些方法发生了变化
self.animal = [LAnimal new];
[self.animal addObserver:self forKeyPath:@"name" options:(NSKeyValueObservingOptionNew) context:@"animal_name"];
NSLog(@"************************");
[self catMethods:NSClassFromString(@"NSKVONotifying_LAnimal")];

打印信息:

2019-12-25 17:06:44.514170+0800 KVODemo[38621:4331805] .cxx_destruct----0x10e85e4d0
2019-12-25 17:06:44.514424+0800 KVODemo[38621:4331805] name----0x10e85e460
2019-12-25 17:06:44.514507+0800 KVODemo[38621:4331805] setName:----0x10e85e490
2019-12-25 17:06:48.723917+0800 KVODemo[38621:4331805] ************************
2019-12-25 17:06:50.295125+0800 KVODemo[38621:4331805] setName:----0x10ebb8b5e
2019-12-25 17:06:50.295294+0800 KVODemo[38621:4331805] class----0x10ebb7592
2019-12-25 17:06:50.295430+0800 KVODemo[38621:4331805] dealloc----0x10ebb7336
2019-12-25 17:06:50.295503+0800 KVODemo[38621:4331805] _isKVOA----0x10ebb732e

通过打印信息可以看到setName方法imp发生了变变化即方法被重写了,并新增了class、dealloc、_isKVOA三个方法,到这可以验证1中的猜测

原理概述:

当类的对象被第一次观察时,会在运行期动态生成一个该类的派生类,并在这个派生类中重写被观察属性的setter方法,通知机制就是在这个setter方法中实现的,例:原类A -> NSKVONotifying_A,每一个对象都有一个isa指针指向当前类,当这个类被观察时,isa指针就会指向新的派生类,被观察属性被赋值时执行的是新类的setter方法,键值观察的通知主要依赖于NSObject的两个方法,willChangeValueForKey和didChangValueForKey,前者是被观察属性发生变化前调用记录就旧值,改变之后后者被调用,最后会调用observeValueForKeyPath代理方法

生活如此美好,今天就点到为止。。。

相关文章

  • iOS面试 - 收藏集 - 掘金

    KVC 与 KVO 拾遗补缺 - iOS - 掘金KVC 和 KVO 是 Cocoa 框架提供的一个非常强的特性,...

  • KVO拾遗

    KVO定义 KVO全称Key Value Observing,根据runtime实现的,是观察者设计模式的一种实现...

  • 经验拾遗之KVO

    一看就懂demo下载,地址:github iOS开发,肯定免不了要接触苹果的黑魔法:kvo,翻译过来有点拗口叫键...

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

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

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

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

  • KVC 与 KVO 拾遗补缺

    KVC 和 KVO 是 Cocoa 框架提供的一个非常强的特性,使用好它们能大大提高我们的开发效率,今天咱们就来探...

  • 20.iOS底层学习之KVO 原理

    本篇提纲1、KVO简介;2、KVO的使用;3、KVO的一些细节;4、KVO的底层原理; KVO简介 KVO全称Ke...

  • 深入理解KVO

    iOS | KVO | Objective-C KVO的本质是什么,如何手动触发KVO? 1.什么是KVO KVO...

  • OC语法:KVO的底层实现

    一、KVO是什么二、怎么使用KVO三、KVO的底层实现四、KVO常见面试题 一、KVO是什么 KVO全称Key-V...

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

网友评论

      本文标题:KVO拾遗

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