美文网首页
KVC底层实现原理

KVC底层实现原理

作者: DPL1024 | 来源:发表于2019-03-03 23:56 被阅读0次

    KVC基本使用

    定义DPLPerson和DPLCat类,DPLCat类拥有weight属性,DPLPerson类拥有agecat属性。

    // DPLCat.h
    #import <Foundation/Foundation.h>
    NS_ASSUME_NONNULL_BEGIN
    @interface DPLCat : NSObject
    @property (nonatomic, assign) int weight;
    @end
    NS_ASSUME_NONNULL_END
    // DPLCat.m
    #import "DPLCat.h"
    @implementation DPLCat
    @end
    
    // DPLPerson.h
    #import <Foundation/Foundation.h>
    #import "DPLCat.h"
    NS_ASSUME_NONNULL_BEGIN
    @interface DPLPerson : NSObject
    @property (nonatomic, assign) int age;
    @property (nonatomic, strong) DPLCat *cat;
    @end
    // DPLPerson.m
    NS_ASSUME_NONNULL_END
    #import "DPLPerson.h"
    @implementation DPLPerson
    @end
    

    通过KVC给属性赋值

    DPLPerson *person = [[DPLPerson alloc] init];
    person.cat = [[DPLCat alloc] init];
         
    // KVC设值   
    [person setValue:@10 forKey:@"age"];
    [person setValue:@3 forKeyPath:@"cat.weight"];
    // KVC取值
    NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
    NSLog(@"person.cat.weight --- %@", [person valueForKeyPath:@"cat.weight"]);
    

    打印

    person.age --- 10
    person.cat.weight --- 3
    

    以上就是KVC基本使用

    KVC设值原理

    • 调用setValue:forKey:
    • 按顺序查找方法setKey:_setKey,如果找到直接执行方法
    • 如果没有找到判断accessInstanceVariablesDirectly方法返回值,如果返回NO,抛出NSUnknownKeyException异常
    • 如果accessInstanceVariablesDirectly方法返回YES,按顺序查找成员变量_key_isKeykeyisKey,如果找到,直接为成员变量赋值
    • 如果没有找到,抛出NSUnknownKeyException异常
    KVC设值流程图.png

    代码验证:

    step1

    设置属性值,系统自动生成set方法,重写set方法

    - (void)setAge:(int)age {
        _age = age;
        
        NSLog(@"setAge: --- age");
    }
    

    执行程序,打印

    setAge: --- age
    

    step2

    删除设置age属性语句,增加_age成员变量。删除重写的set方法,增加_set方法

    //@property (nonatomic, assign) int age;
    
    @public
    int _age;
    
    //- (void)setAge:(int)age {
    //    _age = age;
    //
    //    NSLog(@"setAge: --- age");
    //}
    
    - (void)setAge:(int)age {
        _age = age;
        
        NSLog(@"_setAge --- age");
    }
    

    执行程序,打印

    _setAge --- age
    

    step3

    set_set方法都删除,实现accessInstanceVariablesDirectly返回NO

    //- (void)setAge:(int)age {
    //    _age = age;
    //
    //    NSLog(@"setAge: --- age");
    //}
    
    //- (void)_setAge:(int)age {
    //    _age = age;
    //
    //    NSLog(@"_setAge --- age");
    //}
    
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    

    执行程序,系统抛出异常

    Terminating app due to uncaught exception 'NSUnknownKeyException'
    

    step4

    accessInstanceVariablesDirectly返回YES,依次验证_key_isKeykeyisKey

        @public
    //    int _age;
    //    int _isAge;
    //    int age;
        int isAge;
    
    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    

    正常打印

    person.age --- 10
    

    step5

    删除所有成员变量

        @public
    //    int _age;
    //    int _isAge;
    //    int age;
    //    int isAge;
    

    执行程序,系统抛出异常

    Terminating app due to uncaught exception 'NSUnknownKeyException'
    

    KVC取值原理

    • 调用valueForKey:方法
    • 按顺序查找方法getKey:keyisKey_key,如果找到,直接调用方法取值
    • 如果没有找到,判断accessInstanceVariablesDirectly方法返回值,如果返回NO,抛出异常NSUnknownKeyException
    • 如果找到,按顺序查找成员变量_key_isKeykeyisKey,如果找到,直接取值
    • 如果没找到,抛出异常NSUnknownKeyException
    KVC取值原理.png

    代码验证

    step1

    删除所有成员变量和属性,实现方法getAge

    
    - (int)getAge {
        NSLog(@"getAge");
        
        return 100;
    }
        
    

    执行程序,打印

    getAge
    person.age --- 100
    

    按照异常步骤,依次验证keyisKey_key方法

    //- (int)getAge {
    //    NSLog(@"getAge");
    //
    //    return 100;
    //}
    
    //- (int)age {
    //    NSLog(@"age");
    //
    //    return 101;
    //}
    
    //- (int)isAge {
    //    NSLog(@"isAge");
    //
    //    return 102;
    //}
    
    - (int)_age {
        NSLog(@"_age");
    
        return 103;
    }
    

    step2

    增加成员变量,为成员变量赋值,实现accessInstanceVariablesDirectly方法,返回NO

    @public
    int _age;
    int _isAge;
    int age;
    int isAge;
    
    DPLPerson *person = [[DPLPerson alloc] init];
    person->_age = 100;
    person->_isAge = 101;
    person->age = 102;
    person->isAge = 103;
    NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
    
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    

    执行程序,抛出异常

    NSUnknownKeyException
    

    step3

    实现accessInstanceVariablesDirectly方法,返回NO

    + (BOOL)accessInstanceVariablesDirectly {
        return YES;
    }
    

    执行程序,打印

    person.age --- 100
    

    step4

    注释掉_age

    @public
    // int _age;
    int _isAge;
    int age;
    int isAge;
    
    DPLPerson *person = [[DPLPerson alloc] init];
    // person->_age = 100;
    person->_isAge = 101;
    person->age = 102;
    person->isAge = 103;
    NSLog(@"person.age --- %@", [person valueForKey:@"age"]);
    

    执行程序,打印

    person.age --- 101
    

    按以上步骤,依次验证ageisAge

    step5

    删除所有成员变量

        @public
    //    int _age;
    //    int _isAge;
    //    int age;
    //    int isAge;
    

    执行程序,抛出异常

    NSUnknownKeyException
    

    KVC与KVO的关系

    KVC和KVO名字看上去就很像,通过KVC修改属性值是否会触发KVO呢?
    答案是肯定的,通过KVC修改属性值确实会触发KVO!

    我们为DPLPerson类增加成员变量_age,并未age增加KVO,通过KVC修改_age的值,观察是否会触发KVC监听。

    @interface DPLPerson : NSObject
    {
        @public
        int _age;
    }
    
    DPLPerson *person = [[DPLPerson alloc] init];
    person->_age = 10;
    [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
    [person setValue:@20 forKey:@"age"];
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        NSLog(@"监听到%@的%@发生了改变 --- change:%@", object, keyPath, change);
    }
    

    执行程序,打印

    监听到<DPLPerson: 0x60000150ef50>的age发生了改变 --- change:{
        kind = 1;
        new = 20;
        old = 10;
    }
    

    直接修改成员变量值,是不会触发KVO;但是,使用KVC修改成员变量值,就会触发KVO。

    在之前的文章《KVO底层实现原理》中已经说过,手动出发KVO需要手动执行两个方法willChangeValueForKey:didChangeValueForKey:,KVO在修改值时,就会调用这两个方法,所有会触发KVO。

    重写DPLPerson类的willChangeValueForKey:didChangeValueForKey:方法

    @implementation DPLPerson
    - (void)willChangeValueForKey:(NSString *)key {
        NSLog(@"willChangeValueForKey");
        [super willChangeValueForKey:key];
    }
    - (void)didChangeValueForKey:(NSString *)key {
        NSLog(@"didChangeValueForKey");
        [super didChangeValueForKey:key];
    }
    @end
    

    执行程序,打印

    willChangeValueForKey
    didChangeValueForKey
    监听到<DPLPerson: 0x6000024c5d40>的age发生了改变 --- change:{
        kind = 1;
        new = 20;
        old = 10;
    }
    

    可以看到,确实调用了这两个方法。

    相关文章

      网友评论

          本文标题:KVC底层实现原理

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