• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

iOS - KVC

武飞扬头像
爱尔兰堤坝
帮助1

KVC 简介

  • KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个Key来访问某个属性
  • 常见的API有
    • - (void)setValue:(id)value forKeyPath:(nonnull NSString *)keyPath;
    • - (void)setValue:(id)value forKey:(nonnull NSString *)key;
    • - (id)valueForKeyPath:(NSString *)keyPath;
    • - (id)valueForKey:(NSString *)key;

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

  1. - (void)setValue:(id)value forKey:(nonnull NSString *)key;
    它同person.age的效果一样,都可以设置属性值,直接将属性名当做key,并设置value,即可对属性进行赋值。
    setValue可省略称@10赋值。
    学新通

  2. - (void)setValue:(id)value forKeyPath:(nonnull NSString *)keyPath;
    同上一个不同的地方在于第二个参数变成了ForKeyPath
    应用场景的区别,现在我们有另外一个Student类,并在Person中引用它
    学新通
    Student类中有一个属性是weight
    学新通
    可以像路径(forKeyPath)一样从person中访问student的weight值。KVC进行多级访问时,直接类似于属性调用一样用点语法进行访问即可。
    学新通

  3. - (id)valueForKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;

获取属性值
同上,ForKeyPath可以像路径(forKeyPath)一样从person中访问student的weight值。
学新通

KVC获取值的过程(valueForKey:的原理)

小demo,我们发现通过KVC修改属性是可以触发KVO的

#import "Observer.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Observer *observer = [[Observer alloc] init];
        MJPerson *person = [[MJPerson alloc] init];
        
        //添加监听
        [person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
        
        //通过KVC修改age属性值
        [person setValue:@10 forKey:@"age"];
        
        //移除KVO监听
        [person removeObserver:observer forKeyPath:@"age"];
        }
    return 0;
}

//Observer.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    NSLog(@"observeValueForKeyPath - %@",change);
}

//输出结果
2021-08-03 10:59:00.770197 0800 KVC2[84489:3592653] observeValueForKeyPath - {
    kind = 1;
    new = 10;
    old = 0;
}

学新通
  1. setValue:forKey:首先会查找有没有setKey: 或 _setAge:方法
    在person中写一下set方法来验证一下,发现果然有被调用
    学新通学新通
    另外一个_setAge:方法就不做证明了
    当找不到着两个方法时,就会查看accessInstanceVariablesDirectly方法的返回值
    (属性值通过set方法更改或者没有set方法通过成员变量赋值修改

  2. 查看accessInstanceVariablesDirectly方法的返回值

  • accessInstanceVariablesDirectly方法如果返回YES,则可以访问成员变量并赋值
  • 访问成员变量时是按顺序访问的,顺序:_key,_isKey,key,isKey
  • accessInstanceVariablesDirectly方法如果返回NO,则抛出异常
  • accessInstanceVariablesDirectly方法的默认返回值是YES
  1. 顺序查找成员变量
    下面来证明一下查找成员变量的时候是顺序访问的(当没有属性set方法时,我们设置一些成员变量)
    首先访问的是_age。
    学新通当我们把_age注释掉时
    学新通
    访问的就是_isAge
    学新通剩下的顺序就不一个一个地试验了
  • 找到了成员变量后直接赋值
  • 没找到成员变量抛出异常

学新通
我们之前说过直接修改成员变量是不会出发KVO的
但是在这里我们可以试验一下,在通过KVC修改属性没有set方法仅有成员变量的情况下,还是可以触发KVO的
所以KVC内部可能还包含(willChangeValueForKey和didChangeValueForKey以及set方法)
在Person中写一下willChangeValueForKey和didChangeValueForKey注释set方法
学新通
学新通

运行可知,确实会调用这两个方法,并且在didChangeValueForKey中调用observer的observeValueForKeyPath:ofObject:change:context:方法,具体猜测是

学新通
类比KVO中_NSSet*ValueAndNotify的内部实现
学新通
我们可以看到几乎是一样的。

KVC取值的过程(valueForKey:的原理)

  • 按照getKey,key,isKey,_key顺序查找方法,找到方法的话就调用
    学新通学新通
    由上面两个图可知,确实是按照这种顺序来查找方法的,这里就不一个一个地试验了

  • 如果没有找到上面四种方法,那么就查看accessInstanceVariablesDirectly方法的返回值
    如果是NO,抛出异常
    如果是YES,看下一步

  • 按照_key、_isKey、key、isKey顺序查找成员变量,找到成员变量直接取值
    学新通
    学新通
    同样由上图可知,查找成员变量时也是是有一定顺序的,就不一个一个地试验了。
    没有找到成员变量的时候,就会抛出异常

  • 总结
    学新通

模型&字典

KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回,获取到字典后可以通过key从字典中获取到value。

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

同样,也可以通过KVC进行批量赋值。在对象调用setValuesForKeysWithDictionary:方法时,可以传入一个包含key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给User对象的属性赋值。

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

字典转模型

如果 model 属性和 dic 不匹配,可以重写方法 -(void)setValue:(id)value forUndefinedKey:(NSString *)key

//StudentModel.h
@interface StudentModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *studentSex;

@end

@implementation StudentModel

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if([key isEqualToString:@"sex"]) {
        self.studentSex = (NSString *)value;
    }
}

@end

学新通

学新通

模型转字典

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgababf
系列文章
更多 icon
同类精品
更多 icon
继续加载