zoukankan      html  css  js  c++  java
  • 复习一下KVC

    一. 前言

    KVC(Key Value Coding)是Cocoa框架为开发者提供的非常强大的工具,简单解释为:键值编码。它依赖于Runtime,在OC的动态性方面发挥了重要作用。
    它主要的功能在于直接通过变量名称字符串来访问成员变量,不管是私有的还是共有的,这也是为什么对于OC来说没有真正的私有变量,因为它们都可以使用KVC访问。

    二. 使用场景

    下面是KVC的一些实用场景,读者可自行编码尝试。

    1.访问私有属性

    例如设置UITextField的placeholder颜色,常规的方法是:

     // 方式一:常规设置
     _nameTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"请输入名字" attributes:@{NSForegroundColorAttributeName : [UIColor redColor]}];
    

    通过KVC的方式设置:

    // 方式二:使用KVC获取私有属性
    _nameTextField.placeholder = @"请输入名字";
    // UILabel *placeHolderLabel = [_nameTextField valueForKey:@"placeholderLabel"];
    UILabel *placeHolderLabel = [_nameTextField valueForKey:@"_placeholderLabel"];
    placeHolderLabel.textColor = UIColor.blackColor;
    [self.view addSubview:_nameTextField];
    

    这里通过valueForKey的方式获取了UITextField的placeholderLabel,然后对该对象的颜色进行设置。如果获取到的placeholderLabel为nil,有几种可能:

    1. key输入有误(重新输入)
    2. 系统实现发生改变
    3. 苹果使用的Lazy Load,导致在使用valueForKey获取的时候还没有初始化(因此这里先赋值placeholder,然后再获取placeholderLabel)

    还是建议使用使用方式一对属性进行设置。站在苹果的角度,它之所以把某些属性设置为私有,就是不想让开发者进行直接修改,后续一旦苹果对系统实现有所更改,那就会导致使用KVC获取的内容失效。
    另外,在使用setValue:forKey:的时候一定要类型统一,比如你通过key获取到的是一个Label,却将string设置为了value,将会crash。
    上面在使用valueForKey:方法的时候参数可以带下划线( _ placeholderLabel ),也可以不带下划线,它的主要区别就是如果使用了带下划线的key,就算类中手动实现了getter方法,也不会执行类中实现的getter方法。如果使用了不带下划线的,将会执行类中getter方法。setter也是如此。

    2.对象关系映射

    在没有比较成熟的第三方Model解析(如Mantle)前,ORM(Object Relational Mapping)可以使用KVC进行处理:

    - (instancetype)init {
        return [self initWithJSONDictionary:nil];
    }
    - (instancetype)initWithJSONDictionary:(NSDictionary *)anDictionary {
        if (self = [super init]) {
            [self setValuesForKeysWithDictionary:anDictionary];
        }
        return self;
    }
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
        NSLog(@"%@",key);
    }
    //
    - (void)objectRelationalMapping {
        NSDictionary *personInfoDictionary = @{
                                               @"name" : @"zhangsan",
                                               @"age" : @"30",
                                               @"school" : @"Hist"
                                               };
        Person *p1 = [[Person alloc] initWithJSONDictionary:personInfoDictionary];
        NSLog(@"%@",p1);
    }
    @end
    

    这里主要使用了setValuesForKeysWithDictionary:方法。需要注意的是,需要实现setValue:forUndefinedKey:方法,因为当字典中包含的key在Person属性中并不一定存在,如果不存在的话,就会调用setValue:forUndefinedKey:。该方法默认抛出NSUndefinedKeyException异常。所以需要对其进行重写,避免Crash。

    3. 使用keyPath实现多级访问

    KVC除了setValue:forKey:方法,还有setValue:forKeypath:方法。具体使用如下:

    _nameTextField.placeholder = @"请输入名字";
    [_nameTextField setValue:UIColor.blackColor forKeyPath:@"placeholderLabel.textColor"];
    

    这里一步操作,就完成了对placeholder颜色的变更。

    4. 安全性访问

    现在Person有一个friends方法,属性声明如下:

    @property (nonatomic, copy) NSMutableArray *friends;
    

    此时可以通过如下的方式进行设置friends:

    NSArray *personsArray = ...;
    Person *zhangsan = [[Person alloc] init];
    zhangsan.name = @"zhangsan";
    [[zhangsan mutableArrayValueForKey:@"friends"] addObjectsFromArray:personsArray];
    

    这样就可以顺利将personsArray赋值给zhangsan的friends。接下来换个操作:将属性改为

    @property (nonatomic, copy) NSArray *friends;
    

    其他代码保持不变。再次执行,依然会给zhangsan.friends赋值。而且没有任何crash或者异常。由此可见,通过mutableArrayValueForKey这种方式进行处理,可以对于不可变的集合类型,提供安全的可变访问,即使是不可变数组,也可以增加数组元素。

    5. 函数操作

    使用KVC,可以很方便地进行一些基本的函数操作,例如:

    NSMutableArray *personsArray = [[NSMutableArray alloc] initWithCapacity:5];
    for (NSInteger i = 0; i < 5; i ++) {
        NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i];
        NSDictionary *personInfoDictionary = @{
                                               @"name" : tempName,
                                               @"age" : @(10 + i),
                                               @"school" : @"Hist"
                                               };
        Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary];
        [personsArray addObject:tempPerson];
    }
    NSNumber *count = [personsArray valueForKeyPath:@"@count"];
    NSNumber *sumAge = [personsArray valueForKeyPath:@"@sum.age"];
    NSNumber *avgAge = [personsArray valueForKeyPath:@"@avg.age"];
    NSNumber *maxAge = [personsArray valueForKeyPath:@"@max.age"];
    NSNumber *minAge = [personsArray valueForKeyPath:@"@min.age"];
    

    其中@表示是数组特有的键,而不是名为count的键。可以使用valueForKeyPath:快速进行计算。还可以进行更复杂的一些计算:

    NSArray *array = @[@"apple", @"banner", @"apple", @"orange"];
    NSLog(@"%@", [array valueForKeyPath:@"@distinctUnionOfObjects.self"]); // orange,apple,banner
    
    NSArray *array1 = @[@[@"apple", @"banner"], @[@"apple", @"orange"]];
    NSLog(@"%@", [array1 valueForKeyPath:@"@unionOfArrays.self"]); // apple,banner,apple,orange
    
    NSMutableArray *personsArray1 = [[NSMutableArray alloc] initWithCapacity:5];
    NSMutableArray *personsArray2 = [[NSMutableArray alloc] initWithCapacity:5];
    for (NSInteger i = 0; i < 5; i ++) {
        NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i];
        NSDictionary *personInfoDictionary = @{
                                               @"name" : tempName,
                                               @"age" : @(10 + i),
                                               @"school" : @"Hist"
                                               };
        Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary];
        i % 2 == 0 ? [personsArray1 addObject:tempPerson] : [personsArray2 addObject:tempPerson];
    }
    NSArray *personsArray = @[personsArray1, personsArray2];
    NSLog(@"%@",[[personsArray valueForKeyPath:@"@unionOfArrays.age"] valueForKeyPath:@"@sum.self"]);  // 60
    

    类似上面的代码,可以使用KVC对复杂的操作进行简单化,而没有必要再使用for循环或者其他遍历操作。

    三. KVC验证

    Person *person = [[Person alloc] init];
    [person setValue:[UIColor redColor] forKey:@"name"];
    

    name是string类型,但是传入一个UIColor类型也没有异常或者Crash产生,这也是一个潜在的问题,一旦按照string使用name,就会出现问题。因此需要对value类型与key是否匹配进行判断。KVC提供了如下的方法:

    Person *person = [[Person alloc] init];
    UIColor *color = [UIColor redColor];
    NSError *error = nil;
    BOOL isValidate = [person validateValue:&color forKey:@"name" error:&error];
    if (isValidate && !error) {
       [person setValue:color forKey:@"name"];
    }
    

    发现依然可以设置成功,validateValue:forKey:error:方法竟然返回了YES。根据官方文档可知,此方法的默认实现将搜索接收方的类,寻找名称匹配validate:error:模式的验证方法。如果为属性定义了这样一个方法,那么validateValue:forKey:error: 的默认实现在需要被验证的时候调用。在为这个属性定义的方法中,你可以根据需要更改输入的值,或者设置默认值等。如果没有实现验证方法,则默认返回YES。因此,我们可以在Person类中实现下面的方法:

    - (BOOL)validateName:(id *)ioValue error:(NSError **)error {
        if ([*ioValue isKindOfClass:[NSString class]]) {
            return YES;
        }
        return NO;
    }
    
  • 相关阅读:
    在Ubuntu下安装Apache
    linux 安装jdk 配置tomcat
    linux命令——rmdir
    linux命令——rm
    WebApi路由解析增加版本控制
    eclipse Dynamic web module相关问题
    mysql 5.7.18 windows zip安装
    微信扫描二维码登录网站技术原理
    Maven实战(八)——常用Maven插件介绍(下)
    Maven实战(七)——常用Maven插件介绍(上)
  • 原文地址:https://www.cnblogs.com/zhanggui/p/11010508.html
Copyright © 2011-2022 走看看