美文网首页
2.KVC-KVO基本使用及底层探究

2.KVC-KVO基本使用及底层探究

作者: 那抹浮沉 | 来源:发表于2020-07-08 01:01 被阅读0次

基础使用

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方法来捕获异常,必要可重写

相关文章

  • 2.KVC-KVO基本使用及底层探究

    基础使用 KVC的使用 简单赋值 复杂赋值 修改私有变量 模型和字典的互相转换 取出多个模型中的某个属性的值 你以...

  • KVO的使用及底层探究

    KVO的使用 KVO使用起来非常简单,三个步骤就搞定啦 1、通过addObserver: forKeyPath: ...

  • 探究ReactiveCocoa底层之RACSubject设计流程

    直接上今天的干货部分,来深入了解RACSubject的底层实现及设计思想。 一、探究RACSubject底层设计思...

  • RunLoop学习总结

    通过以下文章学习记录 关于Runloop的原理探究及基本使用 深入理解RunLoop RunLoop完全指南 Ru...

  • OC对象探究03:isa 解析

    提前准备 为了探究对象在底层的实现方式,此次使用clang来进行探究。相关语句clang -rewrite-obj...

  • 关于Runloop的原理探究及基本使用

    一、什么是runloop 字面意思是“消息循环、运行循环”。它不是线程,但它和线程息息相关。一般来讲,一个线程一次...

  • Javafx底层原理探索过程

    经常用一些图形界面久了,难免会去思考这些框架的底层原理。在探究这些底层原理的时候,记录一些探究的过程! 使用的框架...

  • iOS底层 - cache原理分析

    iOS开发底层探究之路 在对Objective-C底层的探究过程中,已经探究过objc_class 结构中的isa...

  • OC对象的本质及底层探究

    Objective-C中的对象,简称OC对象,主要分为3种: instance对象(实例对象) class对象(类...

  • [HTTP] 协议底层文本格式

    平时使用http都是直接使用各种库区发起和接收http请求, 对于http底层格式的探究不多, 这次详细了解了一些...

网友评论

      本文标题:2.KVC-KVO基本使用及底层探究

      本文链接:https://www.haomeiwen.com/subject/bxgtcktx.html