美文网首页
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基本使用及底层探究

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