iOS NSKeyValueCoding 详解

·  阅读 453

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第24天,点击查看活动详情

一、前言

KVC(Key-value coding)键值编码,是对NSObjcet的扩展,分类名为 : NSKeyValueCoding

二、常用的方法说明

// 1. 将键字符串key所对应的属性的值设置为value。不能设定属性值时,将会引起接收器调用方法2
- (void)setValue:(nullable id)value forKey:(NSString *)key

// 2. 当属性值设置失败,调用此方法
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key

// 3. 返回标识属性的键字符串所对应的值。如果获取失败,将会引起接收器调用方法4
- (nullable id)valueForKey:(NSString *)key

// 4. 取值失败,调用此方法
- (nullable id)valueForUndefinedKey:(NSString *)key

// 5. 在键字符串key所对应的"标量"型属性值设为nil,调用此方法,并抛出NSInvalidArgumentException异常(可demo测试)
- (void)setNilValueForKey:(NSString *)key

// 6. 默认返回值YES,代表如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly
复制代码

标量 : 属性中的单纯的数值(整数、实数、布尔值等)

在赋值的时候
如果是结构体,必须包装成NSValue实例
如果是标量型属性,必须包装成NSNumber实例

二、setValue:forKey:底层原理

image.png

原理如下:

  1. 先找相关方法 set<Key>:_set<Key>:setIs<Key>:,如果有,优先调用setter方法完成赋值(注意:set后面的键的第一字字母必须是大写!!)
  2. 如没有找到相关方法 + (BOOL)accessInstanceVariablesDirectly,判断是否可以直接方法成员变量
  3. 如果判断是NO,直接执行KVC的setValue:forUndefinedKey:(系统抛出异常,未定义key)
  4. 如果判断是YES,继续找相关的_<key>_is<Key><key>is<Key>
  5. 方法或成员 都不存在,setValue:forUndefinedKey:方法,默认抛出异常

三、valueForKey:底层原理

image.png

原理如下:

  1. 先找相关方法 get<Key>:Key:countOfKey, objectInKeyAtIndex
  2. 如没有找到相关方法+ (BOOL)accessInstanceVariablesDirectly,判断是否可以直接方法成员变量
  3. 如果上面判断为NO 直接执行KVC的setValue:forUndefinedKey:(系统抛出异常,未定义key)
  4. 如果判断是YES 继续找相关的_<key>_is<Key><key>is<Key>
  5. 方法或成员 都不存在,setValue:forUndefinedKey:方法,默认抛出异常

四、KVC 为什么能能够触发 KVO

KVC 只所以能够触发 KVO,那是因为 在 KVC 底层有手动触发 KVO的代码,在前面的例子添加方法 willChangeValueForKeydidChangeValueForKey可得到验证。

[person willChangeValueForKey:@"age"];
person->_age = 2;
[person didChangeValueForKey:@"age"];
复制代码

五、使用场景

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
)
复制代码
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改