iOS:KVC原理分析

目录
一,基本知识
二,setValue:forKey:底层原理
三,valueForKey:底层原理
四,触发KVO
五,运算符
六,使用场景
七,异常处理

一,基础知识

1,概念

KVCKey-Value-Coding的缩写,意思是键值编码,作用是通过名称来访问对象的属性

2,使用

@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Dog
@end

@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) Dog *dog;
@end

@implementation Person
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    [person setValue:@(1) forKey:@"age"];
    NSLog(@"age: %@", [person valueForKey:@"age"]);
    
    person.dog = [Dog new];
    [person setValue:@"xiaoHuang" forKeyPath:@"dog.name"];
    NSLog(@"name: %@", [person valueForKeyPath:@"dog.name"]);
}

// 打印
age: 1
name: xiaoHuang
二,setValue:forKey:底层原理
setValue:forKey:

下面运行代码来验证一下

1,顺序查找set方法

@implementation Person
- (void)setAge:(NSInteger)age {
    NSLog(@"setAge---%zd", age);
}
- (void)_setAge:(NSInteger)age {
    NSLog(@"_setAge---%zd", age);
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    [person setValue:@(1) forKey:@"age"];
}

// 打印
setAge---1

2,查看accessInstanceVariablesDirectly方法的返回值(默认返回YES

@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    [person setValue:@(1) forKey:@"age"];
}

// crash信息
*** Terminating app due to uncaught exception 'NSUnknownKeyException', 
reason: '[ setValue:forUndefinedKey:]: 
this class is not key value coding-compliant for the key age.'

3,顺序查找成员变量

@interface Person : NSObject
{
    @public
    NSInteger _age;
    NSInteger _isAge;
    NSInteger age;
    NSInteger isAge;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    [person setValue:@(1) forKey:@"age"];
    NSLog(@"%zd---%zd---%zd---%zd", person->_age, person->_isAge, person->age, person->isAge);
}

// 打印
1---0---0---0
三,valueForKey:底层原理
valueForKey:

下面运行代码来验证一下

1,顺序查找get方法

@implementation Person
- (NSInteger)getAge {
    return 1;
}
- (NSInteger)age {
    return 2;
}
- (NSInteger)isAge {
    return 3;
}
- (NSInteger)_age {
    return 4;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    NSLog(@"age---%@", [person valueForKey:@"age"]);
}

// 打印
age---1

2,查看accessInstanceVariablesDirectly方法的返回值(默认返回YES

@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    NSLog(@"age---%@", [person valueForKey:@"age"]);
}

// crash信息
*** Terminating app due to uncaught exception 'NSUnknownKeyException', 
reason: '[ valueForUndefinedKey:]: 
this class is not key value coding-compliant for the key age.'

3,顺序查找成员变量

@interface Person : NSObject
{
    @public
    NSInteger _age;
    NSInteger _isAge;
    NSInteger age;
    NSInteger isAge;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    person->_age = 1;
    person->_isAge = 2;
    person->age = 3;
    person->isAge = 4;
    NSLog(@"age---%@", [person valueForKey:@"age"]);
}

// 打印
age---1
四,触发KVO
@interface Person : NSObject
{
    @public
    NSInteger _age;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Person *person = [Person new];
    person->_age = 1;
    [person addObserver:self
             forKeyPath:@"age"
                options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                context:@"111"];
    [person setValue:@(2) forKey:@"age"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    if (context == @"111") {
        NSLog(@"%@---%@---%@", object, keyPath, change);
    }
}

// 打印
---age---{
    kind = 1;
    new = 2;
    old = 1;
}

由上可知,[person setValue:@(2) forKey:@"age"]的内部实现如下

[person willChangeValueForKey:@"age"];
person->_age = 2;
[person didChangeValueForKey:@"age"];
五,运算符

1,基本运算符

@count:数量
@sum:总和
@avg:平均值
@max:最大值
@min:最小值

Person *p1 = [Person new];
p1.age = 1;
Person *p2 = [Person new];
p2.age = 2;
Person *p3 = [Person new];
p3.age = 3;
NSArray *pArray = @[p1, p2, p3];

NSLog(@"count---%@", [pArray valueForKeyPath:@"@count.age"]);
NSLog(@"sum---%@", [pArray valueForKeyPath:@"@sum.age"]);
NSLog(@"avg---%@", [pArray valueForKeyPath:@"@avg.age"]);
NSLog(@"max---%@", [pArray valueForKeyPath:@"@max.age"]);
NSLog(@"min---%@", [pArray valueForKeyPath:@"@min.age"]);

// 打印
count---3
sum---6
avg---2
max---3
min---1

2,对象运算符

@unionOfObjects:返回数组中对象某属性值的数组(不去除重复值)
@distinctUnionOfObjects:返回数组中对象某属性值的数组(去除重复值)

Person *p1 = [Person new];
p1.age = 1;
Person *p2 = [Person new];
p2.age = 1;
Person *p3 = [Person new];
p3.age = 3;
NSArray *pArray = @[p1, p2, p3];

NSLog(@"unionOfObjects---%@", [pArray valueForKeyPath:@"@unionOfObjects.age"]);
NSLog(@"distinctUnionOfObjects---%@", [pArray valueForKeyPath:@"@distinctUnionOfObjects.age"]);

// 打印
unionOfObjects---(
    1,
    1,
    3
)
distinctUnionOfObjects---(
    3,
    1
)

3,集合运算符

@unionOfArrays:返回二维数组中对象某属性值的数组(不去除重复值)
@distinctUnionOfArrays:返回二维数组中对象某属性值的数组(去除重复值)

Person *p1 = [Person new];
p1.age = @"1";
Person *p2 = [Person new];
p2.age = @"1";
Person *p3 = [Person new];
p3.age = @"1";

Person *p4 = [Person new];
p4.age = @"4";
Person *p5 = [Person new];
p5.age = @"5";
Person *p6 = [Person new];
p6.age = @"6";

NSArray *pArray1 = @[p1, p2];
NSArray *pArray2 = @[p3, p4];
NSArray *pArray3 = @[p5, p6];
NSArray *totalArray = @[pArray1, pArray2, pArray3];

NSLog(@"unionOfArrays---%@", [totalArray valueForKeyPath:@"@unionOfArrays.age"]);
NSLog(@"distinctUnionOfArrays---%@", [totalArray valueForKeyPath:@"@distinctUnionOfArrays.age"]);

// 打印
unionOfArrays---(
    1,
    1,
    1,
    4,
    5,
    6
)
distinctUnionOfArrays---(
    1,
    6,
    4,
    5
)
六,使用场景

1,修改系统控件的样式

UITextField没有提供修改placeholder样式的API,但KVC可以访问类的私有属性,我们可以借助这一特性来修改其样式

[self.textField setValue:UIColor.redColor
              forKeyPath:@"_placeholderLabel.textColor"];
修改前后

2,字典模型互转

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (NSString *)description {
    return [NSString stringWithFormat:@"name:%@, age:%zd", _name, _age];
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];

    NSDictionary *dict = @{@"name" : @"zhangSan",
                           @"age"  : @(1)};
    
    Person *person = [Person new];
    [person setValuesForKeysWithDictionary:dict];
    NSLog(@"字典转模型---%@", person);
    
    NSDictionary *newDict = [person dictionaryWithValuesForKeys:@[@"name", @"age"]];
    NSLog(@"模型转字典---%@", newDict);
}

// 打印
字典转模型---name:zhangSan, age:1
模型转字典---{
    age = 1;
    name = zhangSan;
}

3,操作集合

NSArray *array = @[@"china", @"english", @"american"];

NSArray *capArray = [array valueForKey:@"capitalizedString"];
NSLog(@"capitalizedString---%@", capArray);
    
NSArray *lengthArray = [array valueForKeyPath:@"capitalizedString.length"];
NSLog(@"capitalizedString.length---%@", lengthArray);

// 打印
capitalizedString---(
    China,
    English,
    American
)
capitalizedString.length---(
    5,
    7,
    8
)
七,异常处理

1,key找不到会引起的crash,重写setValue:forUndefinedKey:valueForUndefinedKey:方法可以避免

@implementation Person
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"没找到%@,无法设值", key);
}
- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"没找到%@,无法取值", key);
    return nil;
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [Person new];
    [person setValue:@(1) forKey:@"age"];
    [person valueForKey:@"age"];
}

// 打印
没找到age,无法设值
没找到age,无法取值

2,把nil赋值给基本数据类型的属性会引起的crash,重写setNilValueForKey:方法可以避免

@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (void)setNilValueForKey:(NSString *)key {
    NSLog(@"%@不能设为nil", key);
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];

    Person *person = [Person new];
    [person setValue:nil forKey:@"age"];
}

// 打印
age不能设为nil

你可能感兴趣的:(iOS:KVC原理分析)