iOS开发基础14-KVC
KVC(Key-Value Coding)深度解析:属性访问、字典转换与集合运算符
KVC(Key-Value Coding)是一种通过字符串键在运行时访问和修改对象属性的机制。它为字典转模型、KVO、Core Data 等技术提供了底层支持,是 Objective-C 运行时的重要特性之一。
一、KVC 简介
KVC 是定义在 NSObject 上的非正式协议(NSKeyValueCoding 分类),允许通过字符串 Key 间接访问对象属性,而非直接调用 setter/getter。这种动态特性在字典转模型、集合操作、数据绑定等场景中极为有用。
Swift 中的 KVC:Swift 类使用 KVC 必须继承自
NSObject,且属性需标记@objc。Swift 原生更推荐使用Codable和KeyPath,但与 UIKit 交互时仍会用到 KVC。
二、KVC 存数据
1. 演示用模型
// Dog.h
@interface Dog : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double price;
@end
// Person.h
@class Dog;
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double money;
@property (nonatomic, strong) Dog *dog;
@end
// Person.m
@implementation Person {
@private
int _age; // 私有成员变量
}
- (void)say { NSLog(@"age = %i", _age); }
@end
2. 基本属性赋值
Objective-C
Person *p = [[Person alloc] init];
// 基本类型会自动装箱/拆箱,传入 NSNumber 即可
[p setValue:@"lmj" forKey:@"name"];
[p setValue:@(668.0) forKey:@"money"];
Swift
class Person: NSObject {
@objc var name: String = ""
@objc var money: Double = 0
@objc var dog: Dog?
}
let p = Person()
p.setValue("lmj", forKey: "name")
p.setValue(668.0, forKey: "money")
3. 多层路径赋值(KeyPath)
对于嵌套对象的属性,使用 setValue:forKeyPath:,用点号分隔路径:
p.dog = [[Dog alloc] init];
[p setValue:@"xiaoqiang" forKeyPath:@"dog.name"];
[p setValue:@(110.0) forKeyPath:@"dog.price"];
p.dog = Dog()
p.setValue("xiaoqiang", forKeyPath: "dog.name")
p.setValue(110.0, forKeyPath: "dog.price")
KeyPath 中每一级对象都不能为
nil,否则赋值无效(不会崩溃,但目标属性不会被设置)。
4. 访问私有成员变量
KVC 可以突破访问修饰符限制,直接访问私有实例变量:
[p setValue:@(30) forKey:@"_age"]; // 直接设置私有变量 _age
[p say]; // 输出: age = 30
前提是
accessInstanceVariablesDirectly返回YES(默认即为 YES)。若重写该方法返回NO,KVC 将不会直接访问实例变量。
5. 字典转模型
setValuesForKeysWithDictionary: 可将字典中的键值对批量赋值给对象属性:
NSDictionary *dict = @{
@"name": @"xxx",
@"money": @(998.1)
};
[p setValuesForKeysWithDictionary:dict];
重要注意事项
-
只做一层转换:
setValuesForKeysWithDictionary:不会自动将嵌套字典转为模型对象。如果字典中@"dog"对应一个NSDictionary,它会被直接赋值给dog属性,导致类型错误。嵌套模型需手动处理或使用MJExtension/YYModel等库。 -
不存在的 key 会崩溃:如果字典中包含对象不存在的 key,默认会调用
setValue:forUndefinedKey:并抛出NSUnknownKeyException。可重写该方法安全处理:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
// 忽略不存在的 key,或打印日志
NSLog(@"⚠️ 未定义的 key: %@", key);
}
三、KVC 取数据
1. 获取单个值
NSString *name = [p valueForKey:@"name"];
double money = [[p valueForKey:@"money"] doubleValue]; // 基本类型返回 NSNumber,需拆箱
2. 多层路径获取
NSString *dogName = [p valueForKeyPath:@"dog.name"];
3. 模型转字典
dictionaryWithValuesForKeys: 将指定属性提取为字典:
NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]];
// 输出: { name = xxx; money = 998.1; }
4. 数组上的 KVC
对数组调用 valueForKeyPath: 时,会对数组中每个元素执行操作,并返回结果数组:
Person *p1 = [[Person alloc] init]; p1.name = @"zs"; p1.money = 111;
Person *p2 = [[Person alloc] init]; p2.name = @"ls"; p2.money = 222;
Person *p3 = [[Person alloc] init]; p3.name = @"ww"; p3.money = 666;
NSArray *people = @[p1, p2, p3];
NSArray *names = [people valueForKeyPath:@"name"];
// 输出: (zs, ls, ww)
5. KVC 集合运算符
在 KeyPath 中使用 @运算符 可对集合进行聚合计算,返回 NSNumber:
| 运算符 | 作用 |
|---|---|
@avg |
平均值 |
@sum |
求和 |
@max |
最大值 |
@min |
最小值 |
@count |
元素个数 |
@distinctUnionOfObjects |
去重后的属性值数组 |
@unionOfObjects |
不去重的属性值数组 |
// 平均值
NSNumber *avgMoney = [people valueForKeyPath:@"@avg.money"];
// 求和
NSNumber *sumMoney = [people valueForKeyPath:@"@sum.money"];
// 最大值 / 最小值
NSNumber *maxMoney = [people valueForKeyPath:@"@max.money"];
NSNumber *minMoney = [people valueForKeyPath:@"@min.money"];
// 去重
NSArray *uniqueNames = [people valueForKeyPath:@"@distinctUnionOfObjects.name"];
四、KVC 底层逻辑
1. valueForKey: 查找流程
以 valueForKey:@"name" 为例,系统按以下顺序查找:
- 查找 getter 方法:依次查找
getName、name、isName(针对 BOOL 类型)。 - 查找集合方法:查找
countOfName、objectInNameAtIndex:(有序集合)或enumeratorOfName、memberOfName:(无序集合),用于代理集合属性。 - 查找实例变量:若
accessInstanceVariablesDirectly返回YES,依次查找_name、_isName、name、isName。 - 都未找到:调用
valueForUndefinedKey:,默认抛出NSUnknownKeyException。
2. setValue:forKey: 查找流程
以 setValue:forKey:@"name" 为例:
- 查找 setter 方法:查找
setName:。 - 查找实例变量:若
accessInstanceVariablesDirectly返回YES,依次查找_name、_isName、name、isName。 - 都未找到:调用
setValue:forUndefinedKey:,默认抛出异常。
3. 与 KVO 的关系
- 通过 KVC 的
setValue:forKey:修改属性时,会自动触发 KVO 通知(前提是属性的automaticallyNotifiesObserversForKey:返回YES,默认即为 YES)。 - 直接访问实例变量(如
_name = xxx)不会触发 KVO,因为绕过了 setter 方法。 - KVC 是 KVO 实现的基础之一,KVO 本质上是通过 swizzle setter 方法实现的。
4. 字典转模型的局限
| 能力 | setValuesForKeysWithDictionary: |
MJExtension / YYModel |
|---|---|---|
| 一层属性转换 | 支持 | 支持 |
| 嵌套模型自动转换 | 不支持 | 支持 |
| 数组中模型转换 | 不支持 | 支持 |
| key 与属性名映射 | 不支持 | 支持(如 mj_replacedKeyFromPropertyName) |
| 不存在 key 的处理 | 崩溃(需重写方法) | 自动忽略 |
五、总结
- KVC 通过字符串 Key 动态访问属性,是 Objective-C 运行时动态特性的体现。
setValue:forKey:/valueForKey:操作单层属性,forKeyPath:支持点语法的多层路径。setValuesForKeysWithDictionary:只做一层转换,嵌套模型需手动处理或使用第三方库。- 数组上的 KVC 支持
@avg、@sum、@max、@min、@distinctUnionOfObjects等集合运算符。 - KVC 可以访问私有成员变量,但依赖
accessInstanceVariablesDirectly的返回值。 - KVC 是 KVO、Core Data、字典转模型等技术的底层基础。
- Swift 中使用 KVC 需继承
NSObject并标记@objc,新代码优先使用Codable和KeyPath。

浙公网安备 33010602011771号