iOS KVC

一、简介

KVC是KeyValue Coding的简称,它是对NSObject的扩展NSKeyValueCoding,我们可以通过字符串的名字(key)来获取和设置属性的机制

二、普通对象&不可变容器

下面介绍一下几个方法,这几个方法的调用过程在 官方文档 有详细的说明。XCode(8.3)的 NSKeyValueCoding.h 头文件的注释中也有说明,我下边绘制了几张流程图

1. valueForKey:

为了使流程图更加清晰,这里没有列出全部的方法,有兴趣,可以查阅下文档,里边说的很详细,最好对照文档看,以便查看没有列出的方法以及详细的调用逻辑

valueForKey: 数据查找过程.png
2. setValue: forKey:
setValue: forKey: 数据设置过程.png

三、可变容器

1. mutableArrayValueForKey:

1.调用方法与调用 -valueForKey: 方法值的查找过程其实是类似的,但是它返回了一个集合代理对象,我们调用集合代理对象的insert或romove等方法就会触发如下图所示的处理过程。

2.这里并没有列出全部的插入,移除方法。文档里也详细说明实现至少一个插入方法和至少一个移除方法(文档里的意思应该是对应的插入移除方法要成对出现),调用 mutableArrayValueForKey: 才会返回集合代理对象

3.此外还有 mutableOrderedSetValueForKey: 与 mutableSetValueForKey: 两个方法,它们分别对有序可变集合与无序可变集合的操作,调用过程与 mutableArrayValueForKey: ;类似,不再详述

mutableArrayValueForKey: 数据查找过程.png

测试样例:

//  KYSKVCMutableArrayTest.h

@interface KYSKVCMutableArrayTest : NSObject

@property (strong ,nonatomic) NSMutableArray<NSString *> *kys;

- (void)insertObject:(id)object inKysAtIndex:(NSUInteger)index;

- (void)removeObjectFromKysAtIndex:(NSUInteger)index;

-(void)addObjectObserverWithObject:(NSString *)str;

-(void)removeLastObjectObserver;

@end

//  KYSKVCMutableArrayTest.m

@implementation KYSKVCMutableArrayTest

-(id)init{
    if (self == [super init]){
        _kys = [NSMutableArray new];
        [self addObserver:self forKeyPath:@"kys" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        
    }
    return self;
}

-(void)dealloc{
    [self removeObserver:self forKeyPath:@"kys"];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
    NSLog(@"收到数组变化通知:%@",change);
}

- (void)insertObject:(id)object inKysAtIndex:(NSUInteger)index {
    NSLog(@"insertObject:inKysAtIndex:%@",@(index));
    [self.kys insertObject:object atIndex:index];
}
- (void)removeObjectFromKysAtIndex:(NSUInteger)index {
    NSLog(@"removeObjectFromKysAtIndex:%@",@(index));
    [self.kys removeObjectAtIndex:index];
}

-(void)addObjectObserverWithObject:(NSString *)str{
    [[self mutableArrayValueForKey:@"kys"] addObject:str];
}

-(void)removeLastObjectObserver{
    [[self mutableArrayValueForKey:@"kys"] removeLastObject];
}

@end

测试


KYSKVCMutableArrayTest *test = [[KYSKVCMutableArrayTest alloc] init];
    
NSMutableArray *mArray = [test mutableArrayValueForKey:@"kys"];

[mArray insertObject:@"000" atIndex:0];
    
[mArray removeObjectAtIndex:0];

打印结果

打印结果.png

利用这个特性我们可以实现对数组变化的监听(KVO)


    KYSKVCMutableArrayTest *test = [[KYSKVCMutableArrayTest alloc] init];
    
    [test addObjectObserverWithObject:@"111"];
    
    [test removeLastObjectObserver];

打印结果

数组变化的监听.png

四、其他方法

  • dictionaryWithValuesForKeys: 传入一组key,返回这组key与value的字典
  • setValuesForKeysWithDictionary:可以通过字典设置一组值
    //这里用字典进行测试,使用一个自定义的模型类也是一样的
    NSMutableDictionary *mDic=[@{@"id":@"001",
                                 @"name":@"kys",
                                 @"des":@"adhkaslkfdladf"} mutableCopy];
    
    NSLog(@"%@",[mDic dictionaryWithValuesForKeys:@[@"id",@"name"]]);
    
    [mDic setValuesForKeysWithDictionary:@{@"id":@"002",@"name":@"kys2"}];
    
    NSLog(@"%@",mDic);

打印结果

  • valueForKeyPath:其实就是先用“.”把各个key提取出来依次查找
  • setValue: forKeyPath: 先用“.”把各个key提取出来,查找到最后一层进行赋值

五、检查正确性

我们可以通过以下两个方法验证设置的值是否正确,如果不正确可以在这两个方法里做出相应的处理

  • validateValue: forKey: error:
  • validateValue: forKeyPath: error:

六、集合操作

这些操作其实是很方便的 官方文档 有相关的样例

1. 常规操作符
  • @sum:值的总和
  • @avg:平均值
  • @count:总个数
  • @max:最大值
  • @min:最小值

NOTE:由于是KeyPath可以这样调用@"@sum.floatValue"

    KYSKVCTest *test1=[[KYSKVCTest alloc] init];
    test1.num=1;
    KYSKVCTest *test2=[[KYSKVCTest alloc] init];
    test2.num=2;
    KYSKVCTest *test3=[[KYSKVCTest alloc] init];
    test3.num=3;
    
    NSArray *array=@[test1,test2,test3];
    NSNumber* sum = [array valueForKeyPath:@"@sum.num"];
    NSNumber* avg = [array valueForKeyPath:@"@avg.num"];
    NSNumber* count = [array valueForKeyPath:@"@count"];
    NSNumber* min = [array valueForKeyPath:@"@min.num"];
    NSNumber* max = [array valueForKeyPath:@"@max.num"];
    
    NSLog(@"sum:%@, avg:%@, count:%@, min:%@, max:%@",sum,avg,count,min,max);
    
    //打印结果:sum:6, avg:2, count:3, min:1, max:3
2. 对象操作
  • @distinctUnionOfObjects:元素唯一,会进行去重
  • @unionOfObjects:不会去重
    KYSKVCTest *test1=[[KYSKVCTest alloc] init];
    test1.num=1;
    KYSKVCTest *test2=[[KYSKVCTest alloc] init];
    test2.num=2;
    KYSKVCTest *test3=[[KYSKVCTest alloc] init];
    test3.num=3;
    KYSKVCTest *test4=[[KYSKVCTest alloc] init];
    test4.num=2;
    
    NSArray *array=@[test1,test2,test3,test4];
    
    NSArray *distinctUnionOfObjectsArray=[array valueForKeyPath:@"@distinctUnionOfObjects.num"];
    NSLog(@"distinctUnionOfObjects:%@",distinctUnionOfObjectsArray);
    
    NSArray *unionOfObjectsArray=[array valueForKeyPath:@"@unionOfObjects.num"];
    NSLog(@"unionOfObjects:%@",unionOfObjectsArray);
    
    /* 打印结果
     distinctUnionOfObjects:(
        3,
        2,
        1
     )
     unionOfObjects:(
        1,
        2,
        3,
        2
     )
     */
3. 集合操作
  • @distinctUnionOfArrays:去重
  • @unionOfArrays:不去重
  • @distinctUnionOfSets:集合无重复的

array与set类似,这里只给出array的样例

    KYSKVCTest *test1=[[KYSKVCTest alloc] init];
    test1.num=1;
    KYSKVCTest *test2=[[KYSKVCTest alloc] init];
    test2.num=2;
    KYSKVCTest *test3=[[KYSKVCTest alloc] init];
    test3.num=3;
    KYSKVCTest *test4=[[KYSKVCTest alloc] init];
    test4.num=2;
    NSArray *array1=@[test1,test2,test3,test4];
    
    KYSKVCTest *test5=[[KYSKVCTest alloc] init];
    test5.num=5;
    KYSKVCTest *test6=[[KYSKVCTest alloc] init];
    test6.num=6;
    KYSKVCTest *test7=[[KYSKVCTest alloc] init];
    test7.num=6;
    KYSKVCTest *test8=[[KYSKVCTest alloc] init];
    test8.num=7;
    
    
    KYSKVCTest *test9=[[KYSKVCTest alloc] init];
    test9.num=9;
    KYSKVCTest *test10=[[KYSKVCTest alloc] init];
    test10.num=9;
    KYSKVCTest *test11=[[KYSKVCTest alloc] init];
    test11.num=10;
    //注意这里的嵌套
    NSArray *array2=@[test5,test6,test7,test8,@[test9,test10,test11]];
    
    NSArray *array=@[array1,array2];
    NSArray *distinctUnionOfArraysArray=[array valueForKeyPath:@"@distinctUnionOfArrays.num"];
    NSLog(@"distinctUnionOfArrays:%@",distinctUnionOfArraysArray);
    NSArray *unionOfArraysArray=[array valueForKeyPath:@"@unionOfArrays.num"];
    NSLog(@"unionOfArrays:%@",unionOfArraysArray);
    
    /* 打印结果
     distinctUnionOfArrays:(
        5,
        1,
        (
            9,
            9,
            10
        ),
        6,
        2,
        7,
        3
     )
     unionOfArrays:(
        1,
        2,
        3,
        2,
        5,
        6,
        6,
        7,
        (
            9,
            9,
            10
        )
     )
     */

七、实现方式

运用isa-swizzling技术,通过isa-swizzling来实现其内部定位查找

八、总结

之前在SDWebImage的源码里,看见直接对数组调用valueForKey:方法,当时不明白什么意思,然后查阅了一下相关的文档,发现很多方法都很有用,可以使我们的代码更加精简。如果文章里哪里有不对的地方,欢迎指出_!

推荐阅读更多精彩内容

  • 什么是KVC? KVC的全称叫Key-Value Coding,也叫做键值编码,在apple官方文档中是这么解释的...
    Joker_King阅读 223评论 0 3
  • 关于KVC的详细介绍可以参考官方文档 键值编码(KVC)是由NSKeyValueCoding非正式协议启用的一种机...
    你duck不必呀阅读 211评论 0 1
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 322评论 0 5
  • 什么是KVC? KVC(Key-value coding)键值编码,单看这个名字可能不太好理解。其实是指iOS的开...
    萨缪阅读 1,420评论 0 7
  • 前言:往往会某项工具WORK,就想究其原理。本文先简单介绍KVC 一、KVC 简介 1.1 KVC 概述 1.KV...
    梦蕊dream阅读 617评论 0 3