zoukankan      html  css  js  c++  java
  • kvc本质

    kvc本质

    KVC

    KVC: 全称Key-Value Coding,也称为键值编码。
    KVC可以通过一个key间接访问某个对象属性。
    KVC有两个特性:

    1. 可以访问私有成员变量;
    2. 可以修改私有或者系统的成员属性;

    KVC有以下四种方法:

    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;
    

    其中,前两个是设置值方法,后面两个是取值方法。
    KeyPath可以使用子类里面的数据。Key不可以。

    我们简单看一下KVC的使用:

    YZPerson *person1 = [[YZPerson alloc] init];
    person1.age = 10;//直接赋值
    [person1 setValue:@"jack" forKey:@"name"];//间接赋值
    [person1 setValue:@"120" forKeyPath:@"weight"];//间接赋值
    
    NSLog(@"person1.age = %@, person1.name = %@, person1.weight = %d", [person1 valueForKey:@"age"], [person1 valueForKeyPath:@"name"], person1.weight);
    

    运行结果:

    2020-02-28 11:51:53.654921+0800 Category[1399:80227] person1.age = 10, person1.name = jack, person1.weight = 120
    

    问:KVC修改属性是否可以触发KVO?

    找个例子我们试一下:

    @interface ViewController ()
    @property (strong, nonatomic) Persion *p1;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Persion *p1 = [[Persion alloc] init];
        self.p1 = p1;
        [self.p1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"context"];
        [self.p1 setValue:@"rose" forKey:@"name"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
    {
        NSLog(@"%@ %@ %@", object, change, context);
    }
    
    - (void)dealloc
    {
        [self.p1 removeObserver:self forKeyPath:@"name"];
    }
    
    @end
    

    运行结果:

    2020-02-28 13:48:56.604542+0800 test001[1432:397700] <Persion: 0x283918fa0> {
        kind = 1;
        new = rose;
        old = "<null>";
    } context
    

    从结果可以看出,使用KVC修改属性值(name),可以触发KVO的监听。

    且,经过验证,[self.p1 setValue:@"rose" forKey:@"name"];这句代码进入了

    • (void)setName:(NSString *)name方法里面。
      也就是KVC的改变属性值,进入了属性的setter方法里面,
      从而在didChangeValueForKey:方法中发送通知,实现KVO的监听。

    并且,即使不写- (void)setName:(NSString *)name方法
    或者不写@property (strong, nonatomic) NSString *name;
    设置KVC的属性值改变,也可以使用KVO监听到属性值的改变,即触发KVO

    因此,可以说,KVC是基于KVO的实现,KVC修改属性是可以触发KVO的。

    KVC的工作流程

    setValue:forKey:大致是这样工作的:

    img

    主要是:
    先找方法
    方法找到了,直接执行;
    方法没有找到,则

    判断是否可以直接访问属性
    如果不可以直接访问属性,则Crash
    如果可以执行访问属性,则

    判断有没有响应的属性值
    如果找到了属性,则直接执行;
    如果没有找到属性值,则Crash

    valueForKey:大致是这样工作的:
    img

    KVC的Crash相关

    问:哪些可能会造成KVC的Crash?

    设置

    设置值的时候,Key找不到,Key为nil,value为nil,value类型不匹配就会Crash
    以下几种方法,都会造成KVC的Crash:

    key 不是对象的属性值,造成崩溃[self.person setValue:@"10" forKey:@"age2"];
    keyPath 不正确,造成崩溃[self.person setValue:@"10" forKeyPath:@"age.xxx"];
    key 为 nil,造成崩溃[self.person setValue:@"10" forKey:nil];
    value 为 nil,造成崩溃[self.person setValue:nil forKey:@"age"];
    value类型不对[self.person setValue:[[NSObject alloc] init] forKey:@"age"];

    在这里插入图片描述

    根据KVC设置时的查找过程,我们发现,当setValue:forKey:执行失败会调用 setValue: forUndefinedKey:方法,并引发崩溃。

    那么,我们可以通过重写setValue: forUndefinedKey:来避免

    1. key 不是对象的属性值
    2. keyPath 不正确
    

    造成的Crash

    在这里插入图片描述

    造成的Crash

    我们可以利用 Method Swizzling 方法,在 NSObject 的分类中将 setValue:forKey:ysc_setValue:forKey: 进行方法交换。然后在自定义的方法中,添加对 key 为 nil 这种类型的判断。

    4. value 为 nil
    

    为非对象设值,造成崩溃 的情况

    在调用 setValue:forKey:方法时,系统如果查找到名为 set:方法的时候,会去检测 value 的参数类型,如果参数类型为 NSNmber 的标量类型或者是 NSValue 的结构类型,但是 value 为 nil 时,会自动调用 setNilValueForKey:方法。
    这个方法的默认实现会引发崩溃。

    所以为了防止这种情况导致的崩溃,我们可以通过重写 setNilValueForKey:来解决。

    - (void)setNilValueForKey:(NSString *)key
    {
        NSLog(@"不能将%@设成nil",key);
    }
    

    取值

    取值的时候,Key找不到
    当取值的时候,如果找不到key或者key为nil,也会Crash
    key 不是对象的属性值,造成崩溃NSLog(@"%@", [self.person valueForKey:@"age2"]);
    keyPath 不正确,造成崩溃NSLog(@"%@", [self.person valueForKeyPath:@"age.xxx"]);
    key 为 nil,造成崩溃NSLog(@"%@", [self.person valueForKey:nil]);
    value 为 nil,NSLog(@"%@", [nil valueForKey:@"age"]);,该方法编译器不通过
    在这里插入图片描述

    1和2可以通过重写valueForUndefinedKey:方法
    3通过方法交换,重写valueForUndefinedKey:,在方法里面加非空判断
    4重写setValue:forKey:方法

    iOS 开发:『Crash 防护系统』(三)KVC 防护

    iOS开发技巧系列—详解KVC(我告诉你KVC的一切)

  • 相关阅读:
    我使用的Chrome插件列表
    从花式swap引出的pointer aliasing问题
    CF Educational Codeforces Round 10 D. Nested Segments 离散化+树状数组
    CF #335 div1 A. Sorting Railway Cars
    Mac 下载安装MySQL
    Mac 安装Tomcat
    CF #CROC 2016
    安全体系(零)—— 加解密算法、消息摘要、消息认证技术、数字签名与公钥证书
    安全体系(一)—— DES算法详解
    JAVA实现用户的权限管理
  • 原文地址:https://www.cnblogs.com/r360/p/15749138.html
Copyright © 2011-2022 走看看