基础使用
KVC的使用
- 简单赋值
- 复杂赋值
- 修改私有变量
- 模型和字典的互相转换
- 取出多个模型中的某个属性的值
- 你以为就这了?
KVO的使用
- 注册成为观察者
- kvo的回调方法
- 移除观察者,释放资源
底层探究
- KVO背后原理
- KVC赋值过程
- KVC取值过程
- 贴代码了
异常情况
- value为空
- key不存在
其他如NSArray,NSSet,NSNumber,有兴趣的自行研究,调用方法有所不同
KVC正确性的验证,有兴趣的同上
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
基础使用
- 基础部分,model类中的属性就不赘述了
- 简单赋值
- (void)easyValuation
{
Major *major = [[Major alloc]init];
//常规赋值
major.heig = @"1.98";
//KVC赋值
[major setValue:@"0.98" forKey:@"heig"];
[major setValue:@"1.88" forKeyPath:@"heig"];
//另外通过KVC可以进行自动类型转换,age是int类型,传入的依旧可以是字符串
[major setValue:@"18" forKey:@"age"];
[major setValue:@"28" forKeyPath:@"age"];
NSLog(@"%@",major.heig);
}
- 复杂赋值
- (void)complexValuation
{
//forKeyPath 包含forKey 的功能,所以使用forKeyPath 就行
//forKeyPath 可以进行内部点语法
//属性一定要有,不然会崩溃
//Student类中有一个Major类的属性
Student * stud = [[Student alloc]init];
stud.major.age = 22;
//KVC赋值的3中方式
[stud.major setValue:@"33" forKeyPath:@"age"];
[stud.major setValue:@"33" forKey:@"age"];
[stud setValue:@"33" forKeyPath:@"major.age"];
NSLog(@"%d",stud.major.age);
}
- 修改私有变量
- (void)modifiPrivatelyVariable
{
//修改类的私有成员变量
Major *major = [[Major alloc]init];
//Major类中有一个私有属性MajorName,强行赋值
[major setValue:@"33" forKey:@"MajorName"];
NSLog(@"%@",major);
}
- 模型和字典的互相转换
- (void)transformModelAndDic
{
Major *major = [[Major alloc]init];
// 字段快速赋值对应的属性(适用于简单的字典转模型) 不建议使用
//原因1:字典中的的key,在类的属性列表中必须有(可以不使用),不然会崩溃
//2.如果模型中带有模型(如字典中嵌套数组或字典),子模型则赋值不成功
NSDictionary *dic = @{@"heig":@"188",
@"age":@18};
//字典转模型
[major setValuesForKeysWithDictionary:dic];
//模型转字典
NSDictionary * dic2 = [major dictionaryWithValuesForKeys:@[@"heig",@"age"]];
//关于字典转模型,MJExtension,YYModel 等优秀第三方框架了解一下
//Mantle需要模型类继承Mantle , JSONModel需要模型类继承JSONModel
}
- 取出多个模型中的某个属性的值
//有这么个场景,电话薄的索引要返回一个字符串数组,就可以从模型数组中这么取出
- (void)sdadwx
{
Major *major1 = [[Major alloc]init];
major1.heig = @"198";
Major *major2 = [[Major alloc]init];
major2.heig = @"197";
Major *major3 = [[Major alloc]init];
major3.heig = @"199";
NSArray *majorAry = @[major1,major2,major3];
//取出同一属性的值
NSArray *allHeig = [majorAry valueForKeyPath:@"heig"];
NSLog(@"%@",allHeig);
}
- 你以为就这了?
//这里抛出一个引子
//点击屏幕改变按钮颜色,当然也可以访问其私有属性进行设置
self.btn = [UIButton buttonWithType:UIButtonTypeCustom];
self.btn.backgroundColor = [UIColor redColor];
self.btn.frame = CGRectMake(0, 500, 100, 40);
[self.view addSubview:self.btn];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.btn setValue:[UIColor blueColor] forKeyPath:@"backgroundColor"];
}
KVO的使用
- KVO-键值观察
//注册成为观察者,监听对象的某个属性的值的改变
- (void)regisObsever
{
[self.student addObserver:self forKeyPath:@"major.MajorName" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
//3秒后改变major.MajorName的值
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.student setValue:@"计算机BBB" forKeyPath:@"major.MajorName"];
});
}
- kvo的回调方法
//监听KeyPath 值的变化
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqual:@"major.MajorName"]) {
//获取前后变化的值
NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
}else{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- 移除观察者,释放资源
-(void)dealloc
{
[self.student removeObserver:self forKeyPath:@"major.MajorName"];
}
扩展
KVO 生效
1.使用 setter 方法改变值 ,KVO 生效会生效
2.使用 setValue:forKey 即 KVC 改变值 KVO 也会生效,因为 KVC 会去 调用 setter 方法
底层探究
- KVO背后原理
当某个类的对象第一次被观察时,系统就会在运行时动态的创建对应于该类的一个派生类,在这个派生来中,系统会重写父类中被观察属性的setter方法
//模拟kvo内部代码,实际上在派生类中
- (void)setHeig:(NSString *)heig
{
//属性改变前调用,通知系统改属性即将发生变化
[self willChangeValueForKey:@"heig"];
_heig = heig;
//属性改变前调用,通知系统改属性已经发生变化
[self didChangeValueForKey:@"heig"];
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:@"heig"]) {
//检测到后,取消变更发送通知的操作
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
父类会将对象的isa指针指向新创建的派生类,因此,这个类的对象就成了派生类的对象了,之后调用属性的setter方法实际上调用的是重写后的setter方法,从而实现了键值通知机制,此外,派生类还重写了dealloc方法来释放内存资源
- KVC赋值过程
1.查找类中是否存在与key对应的 setKey _setKey setIsKey方法,存在直接调用,进行赋值
2.若找不到,查看accessInstanceVariablesDirectly方法返回值,默认是YES,开启间接访问成员变量,为NO则为不允许
3.如果为NO,就会调用 setValue: forUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
4.如果为YES,继续寻找成员变量_key _isKey key isKey
5.若未找到,就会调用 setValue: forUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
6.若找到,找到即停,进行赋值,取值的时候,找对应的get方法,比如找到_key,并赋值,
取值就会调用_key方法取值(只有get方法中_key是有值的),而不是从最开始的get方法 getKey 中取值
- KVC取值过程
1.查找类中是否存在与key对应的 getKey key isKey _key 依次调用这些get方法,找到的话直接调用,
2.若找不到,查看accessInstanceVariablesDirectly 方法返回值,默认是YES,开启间接访问成员变量,为NO则为不允许
3.如果为NO,就会调用 valueForUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
4.如果为YES,继续寻找成员变量_key _isKey key isKey
5.若未找到,就会调用 valueForUndefinedKey:方法,并抛出一个异常,如有需要可以重写这个方法
6.若找到,找到即停,进行取值的
- 贴代码了
- Student .h
#import <Foundation/Foundation.h>
@interface Student : NSObject
{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@property (nonatomic, strong)Major *major;
@end
- Student .m
#import "Student.h"
@implementation Student
//苹果默认you实现
//set相关
- (void)setName:(NSString *)name
{
NSLog(@"%s",__func__);
}
- (void)_setName:(NSString *)name
{
NSLog(@"%s",__func__);
}
- (void)_setIsName:(NSString *)isName
{
NSLog(@"%s",__func__);
}
//get 相关
- (NSString *)getName{
NSLog(@"%s",__func__);//[Student getName]
//将SEL对象转为NSString对象
NSLog(@"%@",NSStringFromSelector(_cmd));//_cmd代表当前方法,输出转化的字符串:getName
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
//是否开启间接访问,默认返回yes
//如果关闭就不会找 _key _isKey key isKey这些成员变量
+ (BOOL)accessInstanceVariablesDirectly
{
return YES;
}
@end
- VC中使用
根据赋值流程和取值流程,依次断点查看即可,下面列出为找到get相关方法时,赋值取值情况
self.student->_name = @"_name";
self.student->_isName = @"_isName";
self.student->name = @"name";
self.student->isName = @"isName";
由于赋值先赋值的是 _name,所以,下面输出是 _name,而不是name
NSLog(@"男男女女女女%@",[self.student valueForKeyPath:@"name"]);
异常情况
- value为空
1.如果给对象赋值可以为nil
2.可是给值类型(基本数据类型)赋值nil,会报错setNilValueForKey - key不存在
1:赋值的key不存在,setValue:forUndefinedKey:方法来捕获异常
2:取值的key不存在,valueForUndefinedKey方法来捕获异常,必要可重写
网友评论