KVC使用
基本使用
声明的类:
// .h文件
@interface ModelStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger sex;
@end
// .m文件
#import "ModelStudent.h"
@interface ModelStudent ()
@property NSString *city;
@end
@implementation ModelStudent
@end
在ViewController里:
self.student1 = [[ModelStudent alloc] init];
// name是公有属性
[_student1 setValue:@"yyy" forKey:@"name"];
NSLog(@"%@", [_student1 valueForKey:@"name"]);
// city是私有属性
[_student1 setValue:@"xian" forKey:@"city"];
NSLog(@"%@", [_student1 valueForKey:@"city"]);
公有私有都可以访问和设置
多重属性赋值
student里加一个dog属性
@interface ModelDog : NSObject
@property (nonatomic, strong) NSString *name;
@end
@interface ModelStudent : NSObject<NSCoding, NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger sex;
@property (nonatomic, strong) ModelDog *dog;
@end
ViewController里:
ModelStudent *student = [[ModelStudent alloc] init];
student.dog = [[ModelDog alloc] init];
[student.dog setValue:@"aa" forKey:@"name"];
NSLog(@"%@", [student.dog valueForKey:@"name"]);
[student setValue:@"ww" forKeyPath:@"dog.name"];
NSLog(@"%@", [student valueForKeyPath:@"dog.name"]);
字典转模型
NSDictionary *dic = @{@"name":@"book", @"age" : @"66"};
StudentModel *model = [[StudentModel alloc] init];
[model setValuesForKeysWithDictionary:dic];
NSLog(@"model.name : %@",model.name);
NSLog(@"model.num : %@",model.age);
但是,如果 model 属性和 dic 不匹配:
第一种情况,model多一个属性:这样程序没问题,model多出的属性会是nil
第二种情况,model少一个属性:程序会崩溃
第三种情况,model的属性和dic不对应,程序会崩溃
也就是只要dic的属性model中没有,就会崩溃,解决办法是重写方法 -(void)setValue:(id)value forUndefinedKey:(NSString *)key
@interface StudentModel : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *studentSex;
@end
@implementation StudentModel
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if([key isEqualToString:@"sex"]){
self.studentSex = (NSString *)value;
}
}
@end
ViewController里:
NSDictionary *dic = @{@"name":@"book", @"age" : @"66", @"sex": @"male"};
StudentModel *model = [[StudentModel alloc] init];
[model setValuesForKeysWithDictionary:dic];
NSLog(@"model.name : %@", model.name);
NSLog(@"model.num : %@", model.age);
NSLog(@"model.sex : %@", model.studentSex);
模型转字典
NSDictionary *dic = @{@"name" : @"book", @"age" : @"66", @"sex" : @"male"};
StudentModel *model = [[StudentModel alloc] init];
[model setValuesForKeysWithDictionary:dic];
NSDictionary *modelDic = [model dictionaryWithValuesForKeys:@[@"name", @"age", @"studentSex"]];
NSLog(@"modelDic : %@", modelDic);
KVC 原理
- (void)viewDidLoad {
ModelStudent *student = [[ModelStudent alloc] init];
// 添加KVO监听
[student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
// 通过KVC修改name属性
[student setValue:@"qqq" forKey:@"name"];
// 移除KVO监听
[student removeObserver:self forKeyPath:@"name"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"触发了KVO - %@", change);
}
我们从KVO原理中可以知道,只要实现willChangeValueForKey:
和didChangeValueForKey:
方法就可以调用KVO方法,即在KVC中调用了这两个方法
验证一下:
// ModelStudent.m
- (void)setName:(NSString *)name {
_name = name;
NSLog(@"setName: - %@", name);
}
- (void)willChangeValueForKey:(NSString *)key {
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey - %@", key);
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey - begin - %@", key);
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end - %@", key);
}
说明是在didChangeValueForKey
中调用了setName:
方法,触发了KVO
打印类看一下:
NSLog(@"%s", object_getClassName(student));
调用setValue:forKey:
方法顺序
当调用setValue:forKey:
时 ,程序会先通过setter方法(也就是setKey:
方法),对属性进行设置;
如果没有找到setKey:
方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly
方法有没有返回YES,该方法默认返回YES;如果重写方法成了NO,则调用- (nullable id)setValueForUndefinedKey:
,抛出异常,程序崩溃。
返回YES就去找成员变量并直接赋值。
// ModelStudent.h
@interface ModelStudent : NSObject {
@public
NSString *name;
NSString *isName;
NSString *_isName;
NSString *_name;
}
@end
// ViewController.m
ModelStudent *student = [[ModelStudent alloc] init];
[student setValue:@"qqq" forKey:@"name"];
NSLog(@"%@", [student valueForKey:@"name"]);
可以看到是先给_name赋值了
我们把_name注释了看看:
@interface ModelStudent : NSObject<NSCoding, NSCopying> {
@public
NSString *name;
NSString *isName;
NSString *_isName;
// NSString *_name;
}
下一个是_isName,剩下的按顺序是:name, isName, 就不在这里一一试了,大家可以自己试试
调用Value:forKey:
方法顺序
按先后顺序搜索getKey
、key
、isKey
、_getKey
、_key
五个方法,若某一个方法被实现,取到的即是方法返回的值,后面的方法不再运行。如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象。
// ModelStudent.m
- (void)getName {
NSLog(@"getName");
}
- (void)name {
NSLog(@"name");
}
- (void)isName {
NSLog(@"isName");
}
- (void)_getName {
NSLog(@"_getName");
}
- (void)_name {
NSLog(@"_name");
}
然后注释getName
方法,看调用哪个方法,这里就不一一验证了
如果五个方法都没有,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly
方法有没有返回YES,该方法默认返回YES;如果重写方法成了NO,则调用- (nullable id)valueForUndefinedKey:
,抛出异常,程序崩溃。
返回YES就去找成员变量并直接取值。
取值顺序为:_key, _isKey, key, isKey,这里也就不验证了