iOS:KVC原理分析

作者: 码小菜 | 来源:发表于2019-11-26 11:07 被阅读0次

    目录
    一,基本知识
    二,setValue:forKey:底层原理
    三,valueForKey:底层原理
    四,触发KVO
    五,运算符
    六,使用场景
    七,异常处理

    一,基础知识

    1,概念

    KVCKey-Value-Coding的缩写,意思是键值编码,作用是通过名称来访问对象的属性

    2,使用

    @interface Dog : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Dog
    @end
    
    @interface Person : NSObject
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, strong) Dog *dog;
    @end
    
    @implementation Person
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        [person setValue:@(1) forKey:@"age"];
        NSLog(@"age: %@", [person valueForKey:@"age"]);
        
        person.dog = [Dog new];
        [person setValue:@"xiaoHuang" forKeyPath:@"dog.name"];
        NSLog(@"name: %@", [person valueForKeyPath:@"dog.name"]);
    }
    
    // 打印
    age: 1
    name: xiaoHuang
    
    二,setValue:forKey:底层原理
    setValue:forKey:

    下面运行代码来验证一下

    1,顺序查找set方法

    @implementation Person
    - (void)setAge:(NSInteger)age {
        NSLog(@"setAge---%zd", age);
    }
    - (void)_setAge:(NSInteger)age {
        NSLog(@"_setAge---%zd", age);
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        [person setValue:@(1) forKey:@"age"];
    }
    
    // 打印
    setAge---1
    

    2,查看accessInstanceVariablesDirectly方法的返回值(默认返回YES

    @implementation Person
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        [person setValue:@(1) forKey:@"age"];
    }
    
    // crash信息
    *** Terminating app due to uncaught exception 'NSUnknownKeyException', 
    reason: '[<Person 0x600003938ce0> setValue:forUndefinedKey:]: 
    this class is not key value coding-compliant for the key age.'
    

    3,顺序查找成员变量

    @interface Person : NSObject
    {
        @public
        NSInteger _age;
        NSInteger _isAge;
        NSInteger age;
        NSInteger isAge;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        [person setValue:@(1) forKey:@"age"];
        NSLog(@"%zd---%zd---%zd---%zd", person->_age, person->_isAge, person->age, person->isAge);
    }
    
    // 打印
    1---0---0---0
    
    三,valueForKey:底层原理
    valueForKey:

    下面运行代码来验证一下

    1,顺序查找get方法

    @implementation Person
    - (NSInteger)getAge {
        return 1;
    }
    - (NSInteger)age {
        return 2;
    }
    - (NSInteger)isAge {
        return 3;
    }
    - (NSInteger)_age {
        return 4;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        NSLog(@"age---%@", [person valueForKey:@"age"]);
    }
    
    // 打印
    age---1
    

    2,查看accessInstanceVariablesDirectly方法的返回值(默认返回YES

    @implementation Person
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        NSLog(@"age---%@", [person valueForKey:@"age"]);
    }
    
    // crash信息
    *** Terminating app due to uncaught exception 'NSUnknownKeyException', 
    reason: '[<Person 0x6000018edc10> valueForUndefinedKey:]: 
    this class is not key value coding-compliant for the key age.'
    

    3,顺序查找成员变量

    @interface Person : NSObject
    {
        @public
        NSInteger _age;
        NSInteger _isAge;
        NSInteger age;
        NSInteger isAge;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        person->_age = 1;
        person->_isAge = 2;
        person->age = 3;
        person->isAge = 4;
        NSLog(@"age---%@", [person valueForKey:@"age"]);
    }
    
    // 打印
    age---1
    
    四,触发KVO
    @interface Person : NSObject
    {
        @public
        NSInteger _age;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        Person *person = [Person new];
        person->_age = 1;
        [person addObserver:self
                 forKeyPath:@"age"
                    options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
                    context:@"111"];
        [person setValue:@(2) forKey:@"age"];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(id)object
                            change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                           context:(void *)context {
        if (context == @"111") {
            NSLog(@"%@---%@---%@", object, keyPath, change);
        }
    }
    
    // 打印
    <Person: 0x6000012f3900>---age---{
        kind = 1;
        new = 2;
        old = 1;
    }
    

    由上可知,[person setValue:@(2) forKey:@"age"]的内部实现如下

    [person willChangeValueForKey:@"age"];
    person->_age = 2;
    [person didChangeValueForKey:@"age"];
    
    五,运算符

    1,基本运算符

    @count:数量
    @sum:总和
    @avg:平均值
    @max:最大值
    @min:最小值

    Person *p1 = [Person new];
    p1.age = 1;
    Person *p2 = [Person new];
    p2.age = 2;
    Person *p3 = [Person new];
    p3.age = 3;
    NSArray *pArray = @[p1, p2, p3];
    
    NSLog(@"count---%@", [pArray valueForKeyPath:@"@count.age"]);
    NSLog(@"sum---%@", [pArray valueForKeyPath:@"@sum.age"]);
    NSLog(@"avg---%@", [pArray valueForKeyPath:@"@avg.age"]);
    NSLog(@"max---%@", [pArray valueForKeyPath:@"@max.age"]);
    NSLog(@"min---%@", [pArray valueForKeyPath:@"@min.age"]);
    
    // 打印
    count---3
    sum---6
    avg---2
    max---3
    min---1
    

    2,对象运算符

    @unionOfObjects:返回数组中对象某属性值的数组(不去除重复值)
    @distinctUnionOfObjects:返回数组中对象某属性值的数组(去除重复值)

    Person *p1 = [Person new];
    p1.age = 1;
    Person *p2 = [Person new];
    p2.age = 1;
    Person *p3 = [Person new];
    p3.age = 3;
    NSArray *pArray = @[p1, p2, p3];
    
    NSLog(@"unionOfObjects---%@", [pArray valueForKeyPath:@"@unionOfObjects.age"]);
    NSLog(@"distinctUnionOfObjects---%@", [pArray valueForKeyPath:@"@distinctUnionOfObjects.age"]);
    
    // 打印
    unionOfObjects---(
        1,
        1,
        3
    )
    distinctUnionOfObjects---(
        3,
        1
    )
    

    3,集合运算符

    @unionOfArrays:返回二维数组中对象某属性值的数组(不去除重复值)
    @distinctUnionOfArrays:返回二维数组中对象某属性值的数组(去除重复值)

    Person *p1 = [Person new];
    p1.age = @"1";
    Person *p2 = [Person new];
    p2.age = @"1";
    Person *p3 = [Person new];
    p3.age = @"1";
    
    Person *p4 = [Person new];
    p4.age = @"4";
    Person *p5 = [Person new];
    p5.age = @"5";
    Person *p6 = [Person new];
    p6.age = @"6";
    
    NSArray *pArray1 = @[p1, p2];
    NSArray *pArray2 = @[p3, p4];
    NSArray *pArray3 = @[p5, p6];
    NSArray *totalArray = @[pArray1, pArray2, pArray3];
    
    NSLog(@"unionOfArrays---%@", [totalArray valueForKeyPath:@"@unionOfArrays.age"]);
    NSLog(@"distinctUnionOfArrays---%@", [totalArray valueForKeyPath:@"@distinctUnionOfArrays.age"]);
    
    // 打印
    unionOfArrays---(
        1,
        1,
        1,
        4,
        5,
        6
    )
    distinctUnionOfArrays---(
        1,
        6,
        4,
        5
    )
    
    六,使用场景

    1,修改系统控件的样式

    UITextField没有提供修改placeholder样式的API,但KVC可以访问类的私有属性,我们可以借助这一特性来修改其样式

    [self.textField setValue:UIColor.redColor
                  forKeyPath:@"_placeholderLabel.textColor"];
    
    修改前后

    2,字典模型互转

    @interface Person : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) NSInteger age;
    @end
    
    @implementation Person
    - (NSString *)description {
        return [NSString stringWithFormat:@"name:%@, age:%zd", _name, _age];
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSDictionary *dict = @{@"name" : @"zhangSan",
                               @"age"  : @(1)};
        
        Person *person = [Person new];
        [person setValuesForKeysWithDictionary:dict];
        NSLog(@"字典转模型---%@", person);
        
        NSDictionary *newDict = [person dictionaryWithValuesForKeys:@[@"name", @"age"]];
        NSLog(@"模型转字典---%@", newDict);
    }
    
    // 打印
    字典转模型---name:zhangSan, age:1
    模型转字典---{
        age = 1;
        name = zhangSan;
    }
    

    3,操作集合

    NSArray *array = @[@"china", @"english", @"american"];
    
    NSArray *capArray = [array valueForKey:@"capitalizedString"];
    NSLog(@"capitalizedString---%@", capArray);
        
    NSArray *lengthArray = [array valueForKeyPath:@"capitalizedString.length"];
    NSLog(@"capitalizedString.length---%@", lengthArray);
    
    // 打印
    capitalizedString---(
        China,
        English,
        American
    )
    capitalizedString.length---(
        5,
        7,
        8
    )
    
    七,异常处理

    1,key找不到会引起的crash,重写setValue:forUndefinedKey:valueForUndefinedKey:方法可以避免

    @implementation Person
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        NSLog(@"没找到%@,无法设值", key);
    }
    - (id)valueForUndefinedKey:(NSString *)key {
        NSLog(@"没找到%@,无法取值", key);
        return nil;
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        Person *person = [Person new];
        [person setValue:@(1) forKey:@"age"];
        [person valueForKey:@"age"];
    }
    
    // 打印
    没找到age,无法设值
    没找到age,无法取值
    

    2,把nil赋值给基本数据类型的属性会引起的crash,重写setNilValueForKey:方法可以避免

    @interface Person : NSObject
    @property (nonatomic, assign) NSInteger age;
    @end
    
    @implementation Person
    - (void)setNilValueForKey:(NSString *)key {
        NSLog(@"%@不能设为nil", key);
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        Person *person = [Person new];
        [person setValue:nil forKey:@"age"];
    }
    
    // 打印
    age不能设为nil
    

    相关文章

      网友评论

        本文标题:iOS:KVC原理分析

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