iOS:KVC原理分析
目录
一,基本知识
二,setValue:forKey:底层原理
三,valueForKey:底层原理
四,触发KVO
五,运算符
六,使用场景
七,异常处理
一,基础知识
1,概念
KVC
是Key-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: '[<Person 0x600003938ce0> 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: '[<Person 0x6000018edc10> 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<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if (context == @"111") {
NSLog(@"%@---%@---%@", object, keyPath, change);
}
}
// 打印
<Person: 0x6000012f3900>---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