KVC 浅析

324 阅读5分钟

KVC

1、KVC 是什么

2、KVC 用来解决什么问题

3、KVC 实现原理是怎么样的

4、KVC 怎么用 ,项目中的应用

5、KVC 在开发中遇到的问题

6、KVC 有哪些特点, 优缺点

解答 :

1、KVC 是什么

KVC全称是Key Value Coding,定义在NSKeyValueCoding.h文件中,是一个非正式协议。KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。

KVC 是 KVO ,Cocoa bindings , Core Data,AppleScript 等技术的基础。

2、KVC 用来解决什么问题

  1. KVC 能在某些场景 简化代码。比如 操作符的应用。
  2. 我们可以利用KVC,访问类的私有变量。 -- 对应的, 如果不想外部直接访问实例变量, 则可以将accessInstanceVariablesDirectly属性赋值为NO。--- 再对应的, 如果一个类设置了 NO , 可以通过Category 来覆盖原始的方法, 返回 YES , 也就能访问了
  3. 修改系统的默认实现 : 可以自定义一个UITabbar对象,然后在内部创建自己想要的视图,并通过layoutSubviews方法在内部进行重新布局。然后通过KVC的方式,将UITabbarController的tabbar属性替换为自定义的类即可。

3、KVC 实现原理

继承自NSObject 的对象,都遵守 NSKeyValueCoding 协议, 而且有一些默认实现的方法。

KVC 在根据字符串修改属性或者实例变量的时候,其实有 调用set方法直接进行变量赋值两个过程。这两个过程都能够 触发 KVO

image-20220125145023124

image-20220125144039191

给这些 属性 或者 实例变量 都能够进行赋值。 且是按照如下顺序(优先级)来完成的 key 的查找 和 赋值的。

[setValue :obj forKey : @"key"]

self.key
_key
_isKey
key
isKey

根据上述的描述, 不难发现 : KVC本质上是操作方法列表以及在内存中查找实例变量

这个blog , 翻译了 协议中 API 执行逻辑的部分。 www.cocoachina.com/articles/22…

4、 KVC 怎么使用

// 常见的 API
- (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)valueForKePath:(NSString *)keyPath;

注意, 参数 必须是对象。

- (id)valueForKey:(NSString *)key; 方法 返回的 id 类型的对象, 会对一些 非对象类型进行 包装。比如 CGPoint ---> NSValue 等

自定义返回对象

对于一些 特殊情况,可以通过 重写方法:

- (id)valueForKey:(NSString *)key

来 对于某些key 自定义返回的数据类型。

禁止外部访问实例

如果这个方法 返回 NO ,运行访问实例变量的时候 就会崩溃。

+ (BOOL)accessInstanceVariablesDirectly{
    return NO;
}

集合运算符

主要分为三类:

  1. 集合操作符:处理集合包含的对象,并根据操作符的不同返回不同的类型,返回值以NSNumber为主。
  2. 数组操作符:根据操作符的条件,将符合条件的对象包含在数组中返回。
  3. 嵌套操作符:处理集合对象中嵌套其他集合对象的情况,返回结果也是一个集合对象。
@interface Transaction : NSObject
@property (nonatomic, strong) NSString *payee;
@property (nonatomic, strong) NSNumber *amount;
@property (nonatomic, strong) NSDate *date;
@end
​
​
@interface BankAccount : NSObject
@property (nonatomic, strong) NSArray *transactions;
@end

集合操作符:

计算平均数

@avg 用来计算集合中right keyPath指定的属性的平均值

NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];

计算集合总数

@count 用来计算集合的总数

NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];

计算总和

@sum 用来计算集合中right keyPath指定的属性的总和

NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];

计算最大值

@max 用来查找集合中right keyPath指定的属性的最大值。

NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];

计算最小值

@min 用来查找集合中right keyPath指定的属性的最小值

NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];

数组操作符

@unionOfObjects 将集合对象中,所有payee对象放在一个数组中并返回。

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];复制代码

@distinctUnionOfObjects 将集合对象中,所有payee对象放在一个数组中,并将数组进行 去重 后返回。

NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];

嵌套操作符

由于嵌套操作符是需要对嵌套的集合对象进行操作,所以新建一个arrayOfArrays对象,其中包含两个数组,数组中存储的都是Transaction类型对象。

NSArray *moreTransactions = ....;
NSArray *arrayOfArrays = @[self.transactions, moreTransactions];

@unionOfArrays 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个数组中返回。

NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];复制代码

@distinctUnionOfArrays 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个数组中,并进行排重。

NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];复制代码

@distinctUnionOfSets 是用来操作集合内部的集合对象,将所有right keyPath对应的对象放在一个set中,并进行排重。

NSSet *collectedPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfSets.payee"];

4.2 项目中的实际应用

  1. 字典转模型

5、KVC 在开发中遇到的问题

key 不存在, 会崩溃。

  1. 通过重写setValue:forUndefinedKey:setNilValueForKey:valueForUndefineKey: 进行异常防护
  2. 在进行 kvc 操作之前, 进行检查 : validateValue: forKey: error:

建议 :

  1. KVC的使用尽量在模块内部
  2. 做好异常防护或参数合法性验证

6、KVC 有哪些特点, 优缺点

优点 :

  1. 更加方便的访问对象的属性,实例变量。
  2. 操作符 提供了一些 便捷的操作对象, 集合的方式。 减少代码量
  3. 可以通过 KVC , 修改系统内部实现。
  4. 是一种解耦的途径.

缺点 :

  1. 不管是公开属性 还是 私有变量 都能够直接访问。 破坏了封装性。
  2. 没有条件防护或不恰当的使用容易引发崩溃
  3. 缺少编译期检查,把异常推迟到运行期间