美文网首页
iOS -- KVC底层剖析及应用场景

iOS -- KVC底层剖析及应用场景

作者: 20b347b28fc9 | 来源:发表于2018-04-19 18:06 被阅读53次

    KVC底层剖析及应用场景


    1.方法介绍

    // @interface NSObject(NSKeyValueCoding)
    // 以下方法都是NSKeyValueCoding.h中的方法
    
    // 赋值
    - (void)setValue:(nullable id)value forKey:(NSString *)key;
    - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
    
    // 取值
    - (id)valueForKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;
    - (nullable id)valueForUndefinedKey:(NSString *)key;
    
    // 字典转模型
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    // 模型转字典
    - (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
    

    2.调用顺序--读取部分

    下面做了简单的调用顺序验证,代码没技术含量,看看就行

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    {
        NSString *_name;
        NSString *_isName;
        NSString *name;
        NSString *isName;
    }
    
    // 创建类方法--目的为了在方法中初始化
    + (instancetype)person;
    
    @end
    
    #import "Person.h"
    
    @implementation Person
    
    + (instancetype)person{
        Person *person = [[Person alloc] init];
        person->_name = @"_name";
        person->_isName = @"_isName";
        person->name = @"name";
        person->isName = @"isName";
        return person;
    }
    
    
    
    - (NSString *)name{
        return @"func name";
    }
    
    - (NSString *)getName{
        return @"func getName";
    }
    
    // 这个方法决定是否可以通过KVC获取私有变量--默认是YES
    + (BOOL)accessInstanceVariablesDirectly{
        return YES;
    }
    
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
        
    }
    
    @end
    
    Person *p = [Person person];
    // 此处调用setValueForKey 来取值
    NSString *str = [p valueForKey:@"name"];
    

    通过setValueForKey调用具体是怎样的顺序呢?

    - 总的原则:先找方法,再找变量
    - 1.先找方法,依次寻找下面方法: -getName   -name
    - 2.如果上面两个方法没有找到,调用+accessInstanceVariablesDirectly
      * 如果返回NO,表示不访问变量,直接调用 -valueForUndefinedKey
      * 如果返回YES,继续下面步骤访问相关变量
    - 3. 访问变量,依次虚招下面变量: _name   _isName  name   isName
    - 4. 如果没找到就调用-valueForUndefinedKey 【runtime为防止这里崩溃,可以通过其他方法补救,具体下面解释】
    

    3.调用顺序--赋值部分

    赋值部分跟取值部分相似,具体区别就在方法名上,区别如下:

    方法名: -setName   -setIsName
    

    4.容器类KVC的情况

    容器类有点区别,除了属性外,会调用下面这两个方法,进行成员的设置

    @interface Person : NSObject
    {
        NSMutableArray *nameArray;
    }
    
    // 创建类方法--目的为了在方法中初始化
    + (instancetype)person;
    @end
    
    
    #import "Person.h"
    
    @implementation Person
    
    // 容器类 方法
    - (NSInteger)countOfNameArray{
        return 2;
    }
    -(id)objectInNameArrayAtIndex:(NSInteger)index{
        if (index == 0) {
            return @"1";
        }
        return @"2";
    }
    
    @end
    
    // 外部调用--触发
    Person *p = [Person person];
    NSArray *array = [p valueForKey:@"nameArray"];
    

    5.基本数据类型KVC的情况

    @interface Person : NSObject
    {
        int age;
    }
    
    // 外部调用--触发
    Person *p = [Person person];
    id age = [p valueForKey:@"age"];
    // 这里真是类型是NSNumber
    

    注意:属性中age是基本数据类型,但是通过KVC获取到值之后,转换成为NSNumber类型

    6.KVC使用场景

    6.1 属性赋值
     - 1. -setValue: forKey
     - 2. -setValue: forKeyPath
    

    这两个方法区别不做赘述,比较初级

    6.2 修改系统私有变量

    这里简答做举例说明,也不做相信代码展示
    1. UITextField的placeholder的颜色啊
    2. UIPageControl的底部显示图片,代码如下

    [pageControl setValue:image1 forKeyPath:@"pageImage"];
    [pageControl setValue:image2 forKeyPath:@"currentPageImage"];
    

    这里做一下runtime补充--获取私有变量的方法

    // 导入头文件
    #import <objc/runtime.h>
    
    - (void)getIvarList{
        unsigned int count = 0;
        
        Ivar * ivars = class_copyIvarList([UIPageControl class], &count);
        for (unsigned int i = 0; i < count; i ++)
        {
            Ivar ivar = ivars[i];
            const char * name = ivar_getName(ivar);
            NSLog(@"%s ", name);
        }
        // 内存管理--释放
        free(ivars);
    }
    
    
    6.3 字典转模型

    字典转模型一般配合下面几个方法

    + (instancetype)personModelWithDict:(NSDictionary *)dict{
        Person *person = [[Person alloc] init];
        [person setValuesForKeysWithDictionary:dict];
        return person;
    }
    
    // 处理特殊的key和value
    - (void)setValue:(id)value forKey:(NSString *)key{
        if ([key isEqualToString:@"id"]) {
            [super setValue:value forKey:@"userId"];
        }
    }
    
    // 容错处理
    - (void)setValue:(id)value forUndefinedKey:(NSString *)key{
    }
    
    6.4 字典转模型
    NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"age"]];
    
    6.5 访问容器类(如数组)中元素的属性值
        Person *p1 = [Person person];
        p1.name = @"name-1";
        
        Person *p2 = [Person person];
        p2.name = @"name-2";
        
        Person *p3 = [Person person];
        p3.name = @"name-3";
    
        NSArray *array = @[p1, p2, p3];
        NSArray *nameArray = [array valueForKey:@"name"];
        NSLog(@"%@", nameArray);
    
    6.6 防止私有变量被外界通过KVC修改

    我们看到了KVC可以修改私有变量,但如何防止通过KVC被修改这些变量呢?这就需要用到我们前面介绍过的一个方法 + (BOOL)accessInstanceVariablesDirectly;
    如果防止被修改,记得返回NO;

    相关文章

      网友评论

          本文标题:iOS -- KVC底层剖析及应用场景

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