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

2018-04-19  本文已影响53人  20b347b28fc9

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;

上一篇下一篇

猜你喜欢

热点阅读