[iOS开发]KVC

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]KVC相关的知识,希望对你有一定的参考价值。

Objective-C支持一种灵活的操作方式,这种方式允许以字符串形式间接操作对象的属性,这种方式的全称是Key Value Coding(KVC),即键值编码。

简单的KVC

最基本的KVC由NSKeyValueCoding协议提供支持,最基本的操作属性的两个方法如下:

  • setValue:属性值forKey:属姓名:为指定的属性设定值
  • valueForKey:属姓名:获取指定属性的值

示例:

//  Dog.h
#import <Foundation/Foundation.h>
#import "Bone.h"

NS_ASSUME_NONNULL_BEGIN

@class Bone;

@interface Dog : NSObject {
    @package
    NSString *gender;
    NSString *_gender;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) Bone *bone;
@property (nonatomic, assign) int weight;

@end
//  Bone.h
#import <Foundation/Foundation.h>

@interface Bone : NSObject

@property (nonatomic, strong) NSString *type;
@property (nonatomic, assign) NSInteger weight;

@end
//若dog声明成属性
	_dog = [[Dog alloc] init];
	_dog.bone = [[Bone alloc] init];
    [_dog setValue:@"jack" forKey:@"name"];
//    [self setValue:@"jake" forKeyPath:@"self.dog.name"];
    [_dog.bone setValue:@"猪骨" forKey:@"type"];
//    [self setValue:@"猪骨" forKeyPath:@"_dog.bone.type"];
	NSLog(@"%@", [_dog valueForKey:@"name"]);
    NSLog(@"%@", [_dog.bone valueForKey:@"type"]);

输出:

jake
猪骨

在KVC编程方式中,无论调用setValue:forKey:方法,还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名。
对于“setValue: forKey:”代码,底层的执行机制如下:

  1. 程序优先考虑调用“setName:属性值”代码通过setter方法完成设置。
  2. 如果该类没有setName:方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量在类的接口部分定义,还是在实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对_name成员变量赋值。
  3. 如果该类既没有setName:方法,也没有定义_name成员变量,那么KVC机制会搜索该类中名为name的成员变量,无论该成员变量在类的接口部分定义,还是在实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对name成员变量赋值。
  4. 如果上面三步都没有找到,那么系统将会执行该对象的setValue:forUndefinedKey:方法。默认的setValue:forUndefinedKey:方法实现就是引发一个异常,这个异常将会导致程序因为异常结束。

对于“valueforKey:”代码,底层的执行机制如下:

  1. 程序优先考虑调用“name”代码来获取getter方法返回值。
  2. 如果该类没有name:方法,那么KVC机制会搜索该类中名为_name的成员变量,无论该成员变量在类的接口部分定义,还是在实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是返回_name成员变量的值。
  3. 如果该类既没有name:方法,也没有定义_name成员变量,那么KVC机制会搜索该类中名为name的成员变量,无论该成员变量在类的接口部分定义,还是在实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是返回name成员变量的值。
  4. 如果上面三步都没有找到,那么系统将会执行该对象的valueForUndefinedKey:方法。默认的valueForUndefinedKey:方法实现就是引发一个异常,这个异常将会导致程序因为异常结束。
//  Dog.m
//类实现部分定义age
#import "Dog.h"

@implementation Dog {
    int age;
}

@end
	[_dog setValue:@5 forKey:@"age"];
    NSLog(@"%@", [_dog valueForKey:@"age"]);

KVC可以正常工作:输出5。

	[_dog setValue:@"f" forKey:@"gender"];
    NSLog(@"%@", _dog->gender);
    NSLog(@"%@", _dog->_gender);

输出:

(null)
f

处理不存在的key

可以在实现文件中重写setValue: forUndefinedKey:方法与valueForUndefinedKey:方法来实现一些自定义行为:

- (id) valueForUndefinedKey:(NSString *)key {
    NSLog(@"尝试访问的key:【%@】并不存在", key);
    return 0;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"尝试设置的key:【%@】并不存在", key);
    NSLog(@"尝试设置的value:%@", value);
}
    [_dog setValue:@"no" forKey:@"hhh"];
    [_dog valueForKey:@"hhh"];

输出:

尝试设置的key:【hhh】并不存在
尝试设置的value:no
尝试访问的key:【hhh】并不存在

处理nil值

可以在实现文件中重写setNilValue:forKey:方法来实现一些自定义行为:

- (void)setNilValueForKey:(NSString *)key {
    if ([key isEqualToString:@"weight"]) {
        //如果尝试将key为weight属性设置为nil
        //将_weight设为0
        _weight = 0;
    } else {
        [super setNilValueForKey:key];
    }
}
	[_dog setValue:nil forKey:@"weight"];
    NSLog(@"%d", _dog.weight);

key路径

KVC除了可操作对象的属性之外,还可以操作对象的“复合属性”。所谓复合属性,KVC机制将其称为key路径。
在KVC协议中操作key路径的方法如下:

  • setValue: forKeyPath:
//若dog声明成属性
	_dog = [[Dog alloc] init];
	_dog.bone = [[Bone alloc] init];
//    [_dog setValue:@"jack" forKey:@"name"];
    [self setValue:@"jake" forKeyPath:@"self.dog.name"];
//    [_dog.bone setValue:@"猪骨" forKey:@"type"];
    [self setValue:@"猪骨" forKeyPath:@"_dog.bone.type"];
    NSLog(@"%@", [_dog valueForKeyPath:@"name"]);
    NSLog(@"%@", [self valueForKeyPath:@"_dog.bone.type"]);

输出:

jake
猪骨

此外,KVC还在其他很多方面有作用:

	//对数组内的字典相同的key取值
    
    NSDictionary *dic1 = @{@"city":@"北京",@"count":@"22"};
    NSDictionary *dic2 = @{@"city":@"上海",@"count":@"18"};
    NSDictionary *dic3 = @{@"city":@"深圳",@"count":@"17"};
    
    NSArray *arr = @[dic1,dic2,dic3];
    
    NSLog(@"city:%@",[arr valueForKeyPath:@"city"]);
    NSLog(@"count:%@",[arr valueForKeyPath:@"count"]);
    
    
    //数组求和sum、求平均average、求最大值max、求最小值min
    NSArray *array = @[@20, @10, @30, @4, @6, @"8"];
    CGFloat sumValue = [[array valueForKeyPath:@"@sum.floatValue"] floatValue];
    NSLog(@"%f", sumValue); // 78
    
    //第一个例子
    NSLog(@"求和:%@",[arr valueForKeyPath:@"@sum.count"]);
    NSLog(@"平均:%@",[arr valueForKeyPath:@"@avg.count"]);
    NSLog(@"最大:%@",[arr valueForKeyPath:@"@max.count"]);
    NSLog(@"最小:%@",[arr valueForKeyPath:@"@min.count"]);
    
    //数组内字符串操作
    NSArray *arrayString = @[@"a", @"bb", @"ccc"];
        
    NSArray *tmp = [arrayString valueForKeyPath:@"uppercaseString"];
    NSArray *tmp2 = [arrayString valueForKey:@"uppercaseString"];
    //valueForKey也可以达到相同目的
    NSArray *lengths = [arrayString valueForKeyPath:@"length"];
    NSLog(@"%@", lengths);
    NSLog(@"%@", tmp);
    
    //数组去重
    NSArray *array2 = @[@"1", @"dd", @"ccc", @"dd", @"1", @"d", @"d"];
    NSArray *rs = [array2 valueForKeyPath:@"@distinctUnionOfObjects.self"];
    NSLog(@"%@", rs);
    //数组内包含字典,去重字典某字段的重复值
    NSArray *array3 = @[@{@"name": @"zhangsan", @"age": @"1"},
                        @{@"name": @"zhangsan", @"age": @"1"},
                        @{@"name": @"lisi", @"age": @"2"}];
    rs = [array3 valueForKeyPath:@"@distinctUnionOfObjects.name"];
    NSLog(@"%@", rs);
    NSArray *rs2 = [array3 valueForKeyPath:@"@distinctUnionOfObjects.age"];
    NSLog(@"%@", rs2);
    
    //多级字典嵌套取值
    //对于 @{key1:@{key2:vale}} 的字典(字典的value是另一个字典),通过 key1.key2 的链式的方式得到最深层的字典的值。
    NSDictionary *dict4 = @{@"name":@"小明",@"age":@"22"};
    NSDictionary *dict5 = @{@"student":dict4};
    NSDictionary *dict6 = @{@"class":dict5};
    NSDictionary *dict7 = @{@"school":dict6};
    
    NSLog(@"%@",[dict7 valueForKeyPath:@"school.class.student.name"]);
    NSLog(@"%@",[dict7 valueForKeyPath:@"school.class.student.age"]);
    
    //数组内模型取值
    NSArray *array4 = @[@"a", @"bb", @"ccc"];

    NSMutableArray *Bones = [NSMutableArray arrayWithCapacity:array4.count];
    for (int i = 0; i < array4.count; i++) {
        Bone *b = [[Bone alloc]init];
        b.type = array4[i];
        b.weight = i;
        [Bones addObject:b];
    }
    NSArray *names = [Bones valueForKeyPath:@"type"];
    NSLog(@"%@", names);

输出结果:

city:(
    "\\U5317\\U4eac",
    "\\U4e0a\\U6d77",
    "\\U6df1\\U5733"
)
count:(
    22,
    18,
    17
)
78.000000
求和:57
平均:19
最大:22
最小:17
(
    1,
    2,
    3
)
(
    A,
    BB,
    CCC
)
(
    dd,
    d,
    1,
    ccc
)
(
    zhangsan,
    lisi
)
(
    1,
    2
)
小明
22
(
    a,
    bb,
    ccc
)

小结:

通过KVC操作对象的性能比通过setter、getter方法操作的性能更差,使用KVC的优势在于编程更加灵活,更适合提炼一些通用性质的代码。由于KVC方式允许通过字符串形式来操作对象的属性,这个字符串既可以是常量,也可以是变量,因此具有极高的灵活性。

以上是关于[iOS开发]KVC的主要内容,如果未能解决你的问题,请参考以下文章

ios开发UI篇—Kvc简单介绍

IOS开发过程中的KVC机制

iOS开发-OC篇-KVC详解

iOS开发中KVCKVO简介

iOS开发中KVCKVO简介

[iOS开发]KVC