• iOS KVO KVC 详解


    什么是KVO

    KVO的本质是key-Value Observing 俗称 健值监听 可以用与监听某个对象属性值的改变。

    如果一个对象想要知道另一个对象属性值的改变 我们就可以使用KVO来实现 具体代码如下

    #import "ViewController.h"
    #import "Person.h"
    
    @interface ViewController ()
    
    @property (nonatomic,strong) Person *p;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *p = [[Person alloc] init];
        p.age = 10;
        self.p = p;
        
        //添加KVO监听
        [self.p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
        
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        self.p.age = 20;
    }
    //当监听对象的属性值发生改变时 会调用
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
        if ([keyPath isEqualToString:@"age"]) {
            NSLog(@"属性值改变了 %@",change);
        }
    }
    
    -(void)dealloc {
        [self.p removeObserver:self forKeyPath:@"age"];
    }
    
    @end

    其实被监听的对象在调用setAge的时候

    1.系统创建继承与被监听对象的子类 并把监听对象的isa指针指向该类类对象

    2.调用该类的setter方法 在这个方法中调用_NSSet*ValueNotify()方法 该方法调用willChangeValueForKey:  super的setter方法 和 didChangeValueForKey方法 *代表类型 比如Int

    3.在didChangeValueForKey 调用监听者的observeValueForKeyPath: ofObject: change: context:方法完成监听

    模拟动态创建的类的代码(伪代码)

    #import "NSKVONotifying_LFPerson.h"
    
    @implementation NSKVONotifying_LFPerson
    
    - (void)setAge:(int)age {
        _NSSetIntValueNotify();
    }
    
    void _NSSetIntValueNotify() {
        [self willChangeValueForKey:@"age"];
        [super setAge:age];
        [self didChangeValueForKey:@"age"];
    }
    - (void)didChangeValueForKey:(NSString *)key {
        //通知监听器属性值发生了改变
        //通知监听器的observeValueForKeyPath: ofObject: change: context:方法
    }

    那么iOS中用什么方式实现对一个对象的KVO?(KVO的本质是什么)

    首先利用RunTime动态生成一个子类 并且让实例对象的isa指针指向该类

    当修改实例对象的属性时 会调用Foundation的_NSSSetXXXValueAndNotify函数

    然后调用WillChangeValueForKey  父类的setter方法

    最后调用didChangeValueForKey 在其内部会调用监听者的observeValueForKeyPath: ofObject: change: context:方法

    如何手动触发KVO呢?

    手动调用 willChangeValueForKey 和 didChangeValueForKey方法 单纯调用didChangeValueForKey是不能触发KVO的 因为是要这个方法的内部会去判断是否已经调用了willChangeValueForKey

    如果没调用 是不会触发监听者的监听方法的.

    KVC的简单使用

    KVC的全称Key-Value Coding 俗称键值编码 可以通过一个key来访问某个属性

    常见的API有

    setValue:forKeyPath:

    setValue:forKey

    valueForKeyPath:

    valueForKey:

    前面两个设置属性值 后面两个是获取属性值的

    #import <Foundation/Foundation.h>
    
    @interface Cat : NSObject
    
    @property (nonatomic,assign) int weight;
    
    @end
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject
    
    @property (nonatomic,assign) int age;
    
    @property (nonatomic,strong) Cat *cat;
    
    @end
    
    NS_ASSUME_NONNULL_END
    #import "ViewController.h"
    #import "Person.h"
    
    @interface ViewController ()
    
    @property (nonatomic,strong) Person *p;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        Person *p = [[Person alloc] init];
        //KVC赋值
        p.cat = [[Cat alloc] init];
        [p setValue:@10 forKey:@"age"];
        [p setValue:@11 forKeyPath:@"cat.weight"];
        
        NSNumber *age = [p valueForKey:@"age"];
        NSNumber *weight = [p valueForKeyPath:@"cat.weight"];
        self.p = p;
        [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    }
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self.p setValue:@20 forKey:@"age"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        NSLog(@"%@",change);
    }
    
    - (void)dealloc
    {
        [self.p removeObserver:self forKeyPath:@"age"];
    }

    通过KVC修改属性会触发KVO吗?

    通过上面的代码 我们可以看到是可以触发KVO的. 为什么会这样 其实我们可以追究一下setValue:forKey:这个方法的原理:

    1.首先会找相关属性的setKey: 或者_setKey方法 也就是setter方法 即使这个属性没被声明 只要实现了setKey:或者_setKey:也会调用

    2.如果这两个方法没被实现 就会访问+(BOOL)accessInstanceVariablesDirectly 方法 返回YES 能够直接访问成员变量 返回NO不能直接访问成员变量 默认的返回值是YES

    3.如果返回NO 会闪退 setValue:forUndefinedKey 报这个错 如果返回YES 就是允许他访问成员变量 那么他会按照 _key, _isKey, key, isKey,顺序查找成员变量

    4.如果找到了 直接赋值 如果找不到会闪退 setValue:forUndefinedKey

    如果我们key是成员变量而不是属性 也会触发KVO 其实KVC内部赋值后应该会自行通知KVO的观察者的 内部实现了willChageForKey:和didChangeForKey

    所以如果我们的KVC是赋值成员变量的话 内部其实是这样的

    [p willChangeValueForKey:@"age"];

    p->_age = 10;

    [p didChangeValueForKey:@"age"];

    KVC的赋值和取值过程是怎样的? 原理是什么?

    取值的过程

    1.首先会找相关属性的setKey: 或者_setKey方法 也就是setter方法 即使这个属性没被声明 只要实现了setKey:或者_setKey:也会调用

    2.如果这两个方法没被实现 就会访问+(BOOL)accessInstanceVariablesDirectly 方法 返回YES 能够直接访问成员变量 返回NO不能直接访问成员变量 默认的返回值是YES

    3.如果返回NO 会闪退 setValue:forUndefinedKey 报这个错 如果返回YES 就是允许他访问成员变量 那么他会按照 _key, _isKey, key, isKey,顺序查找成员变量

    4.如果找到了 直接赋值 如果找不到会闪退 setValue:forUndefinedKey

    获取值的过程

    1.按照 getKey, key, isKey _key的方法去查找 如果有 就调用这个方法 返回值

    2.没找到方法 会访问+(BOOL)accessInstanceVariablesDirectly 返回NO 调用valueForUndeFinedKey:抛出异常 NSUnKnownKeyException

    3.如果返回YES则按顺序查找_key _isKey key isKey成员变量 如果找到返回该值 找不到 调用valueForUndeFinedKey:抛出异常 NSUnKnownKeyException

  • 相关阅读:
    1114 Family Property (25 分)(并查集)
    【专题复习4:Dijkstra】1003、1018、1030、1072、1087、1111
    1133 Splitting A Linked List (25 分)(链表)
    【专题复习5:并查集】1107、1114、1118
    Java Window 下安装 java
    OpenShift4 节点TimeZone修改
    PHP魔术方法
    .NET中的HashSet
    格式化字符串
    Jetpack Compose
  • 原文地址:https://www.cnblogs.com/huanying2000/p/13934695.html
Copyright © 2020-2023  润新知