KVC初探
1.一般setter方法
代码:
ViewController.m
person.name = @"LG_Cooci";
person.age = 18;
person->myName = @"cooci";
NSLog(@"%@ - %d - %@",person.name,person.age,person->myName);
打印结果:
Janice - 18 - ty
2.Key-Value Coding (KVC) : 基本类型赋值
代码:
ViewController.m
[person setValue:@"Janice" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"ty" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);
打印结果:
Janice - 19 - ty
3.KVC:集合类型赋值
代码:
ViewController.m
//不可变数组
person.array = @[@"1",@"2",@"3"];
NSArray *array = [person valueForKey:@"array"];
// 用 array 的值创建一个新的数组
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"%@",[person valueForKey:@"array"]);
//可变数组
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"200";
NSLog(@"%@",[person valueForKey:@"array"]);
打印结果:
(
100,
2,
3
)
(
200,
2,
3
)
4.KVC - 访问非对象属性
代码:
LGPerson.h
#import <Foundation/Foundation.h>
#import "LGStudent.h"
NS_ASSUME_NONNULL_BEGIN
typedef struct {
float x, y, z;
} ThreeFloats;
@interface LGPerson
@property (nonatomic) ThreeFloats threeFloats;
@end
ViewController.m
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"%@",reslut);
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"%f - %f - %f",th.x,th.y,th.z);
打印结果:
{length = 12, bytes = 0x0000803f0000004000004040}
1.000000 - 2.000000 - 3.000000
5.KVC - 层层访问
LGStudent *student = [[LGStudent alloc] init];
student.subject = @"语文";
person.student = student;
[person setValue:@"数学" forKeyPath:@"student.subject"];
NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
打印结果:
数学
KVC原理设值过程
这个过程中主要用成员变量来探索,为什么要使用成员变量呢,因为属性在赋值的过程中本来就会生成getter 和 setter 方法了,所以再用属性探索KVC赋值,会不好分辩。
根据官方文档可知 KVC setter 过程中查找赋值的方法如下:
1、set<Key>: 或则 _set<key>
2、如果找不到第一步,查看 accessInstanceVariablesDirectly 返回是否为YES,如果返回YES,就继续查找一下方法:_<key>, _is<key>,<key>,或则 is<key>
依次来验证一下:
1、先找setter
代码:
LGPerson.h
#import <Foundation/Foundation.h>
#import "LGStudent.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
LGPerson.m
#import "LGPerson.h"
@implementation LGPerson
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
@end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"Janice" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
}
运行结果:
2、 当 accessInstanceVariablesDirectly 为 YES
LGPerson.m 中代码稍微做一点改动,如下:
//MARK: - setKey. 的流程分析
//注释掉
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
打印结果:
然后代码再做一点改动,如下
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
打印结果:
2、 当 accessInstanceVariablesDirectly 为 NO 时,做如下改动
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
打印结果:
以上,打印估计有注意到了我下面打印的值全部都为 null,这是因为在重写方法时,并没有复制的哈,如_name = name;
下面将重写方法全都注释掉,如下,再来打印成员变量的值,再将 accessInstanceVariablesDirectly 然后YES。
代码:
LGPerson.m
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"Janice" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
}
打印结果:
打印: LGPerson.h
#import <Foundation/Foundation.h>
#import "LGStudent.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
@public
// NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"Janice" forKey:@"name"];
// NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
}
打印结果:
再验证最后一个
#import <Foundation/Foundation.h>
#import "LGStudent.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject{
@public
// NSString *_name;
// NSString *_isName;
// NSString *name;
NSString *isName;
}
@end
ViewController.m
#import "ViewController.h"
#import "LGPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"Janice" forKey:@"name"];
// NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
// NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
// NSLog(@"%@-%@",person->name,person->isName);
// NSLog(@"%@",person->isName);
}
打印结果:
KVC原理取值过程
官方文档的查找流程如下,get<key>, <key>, is<key>,_<key>
代码:
LGPerson.h
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
LGPerson.m
- (NSString *)getName{
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 2: KVC - 取值的过程
//先赋值不同的值
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
//取值
NSLog(@"取值:%@",[person valueForKey:@"name"]);
}
打印结果:
再验证一个,就将getName注释掉,走下一个方法
LGPerson.m
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,
//- (NSString *)getName{
// return NSStringFromSelector(_cmd);
//}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
打印结果:
KVC设置与取值总结:
KVC 设置过程:
1、判断是否存在'set<key>' 或者 '_set<key>'(带下划线的属性)'setls<key>'
2、如果没有条件 1,(简单访问方式)
2.1、判断 'accessInstanceVariablesDirectly' 是否存在,返回 'YES'
2.2、判断 '_<key>','_is<key>','<key>','is<key>'等实例变量
2.3、直接给这些实例变量设置
3、'setValue:forUndefinedKey:'报错!
KVC 取值过程:
1、如果找到'get<key>',<key>,'is<key>','_<key>'这几个方法就跳到 '第五步'
2、如果没有条件 1 ,开始是否是 NSArray 判断
3、是否是 NSSet 判断
4、非集合类型
4.1、'accessInstanceVariablesDirectly' 返回 YES
4.2、'_<key>,_is<key>,,is<key>'这些实例属性
4.3、如果找到,直接获取实例变量的值,然后继续执行步骤 5
5、细节处理
5.1、如果检索到的属性值是对象指针,则只需返回结果
5.2、如果该值是 NSNumber 支持的标量类型,则将其存储在 NSNumber 实例中并返回它
5.3、如果结果是NSNumber 不支持的标量类型,则转换为 NSValue 对象并返回
6、'valueForUndefineKey'报错!!!
7、集合类型的还需要操作!
KVC使用小技巧
1、KVC 自动转换类型
如,
[person setValue:@"20" forKey:@"age"];
age 为 int 类型,但是给了一个 NSString 类型,于是,便会自动转换类型为 __NSCFNumber
2、可以对int,NSValue 类型设置空值,对NSString设空值变不会走设置的任何方法
当找不到时可以用一下方法可以检测到
- (void)setNilValueForKey:(NSString *)key;
3、找不到Key
当找不到时可以用一下方法可以检测到
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
4、取值时 - 找不到 key
当找不到时可以用一下方法可以检测到
- (nullable id)valueForUndefinedKey:(NSString *)key;
5、键值验证
在此方法可以做一些容错处理
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;