【iOS】KVC 与 KVO 的基本了解与使用
文章目录
- 前言
- 一、KVC(Key-Value Coding)键值编码
- 概念理解
- 四个常用方法
- key 与 keyPath 区别
- KVC 查找与赋值流程
- (1)查找 set 方法
- (2)未找到 set 方法时
- (3)如果都没找到
- (4)特殊情况:给基本类型赋 nil
- KVC 支持数值与结构体类型
- KVC 验证机制
- KVC 对集合的强大支持
- 基本运算符:
- 对象运算符:
- KVC 操作字典
- KVC 的典型应用场景
- 二、KVO(Key-Value Observing)键值观察
- 概念回顾
- 实现步骤
- 监听机制(内部原理)
- KVO 与通知的区别
- KVO 的优缺点
- 三、KVC 与 KVO 的关系
- 四、总结
前言
在 iOS 开发中,我们常用 Block、Delegate、通知等方式进行数据传递,这些传值方式在我前面博客都予以介绍过【iOS】多界面传值(五大传值方式)
但其实,Objective-C 还有一对我没有提到过的非常重要的两个东西:KVC(键值编码) 和 KVO(键值观察)
简单介绍的话就是
- KVC 允许我们通过字符串(key)访问属性;
- KVO 允许我们自动监听属性值的变化。
它们一个负责“访问”,一个负责“观察”,是 Cocoa 动态机制的核心。
Cocoa即objective-c框架体系,如UIKit,Foundation,Appkit等
动态机制:程序的某些行为不是在编译时就固定好的,而是在运行时才决定的,总结如下:
- 变量在运行时才确定其真实类型
- 方法调用在运行时才决定调用哪个实现
- 类,方法甚至代码可以在运行时被加载或替换(kvo动态创建子类)
一、KVC(Key-Value Coding)键值编码
概念理解
KVC 是一种通过字符串键名来访问对象属性的机制,而不是直接调用 getter / setter。
例如我们平时这样写:
person.name = @"Tom";
NSLog(@"%@", person.name);
使用 KVC 可以写成:
[person setValue:@"Tom" forKey:@"name"];
NSLog(@"%@", [person valueForKey:@"name"]);
用字符串当作“钥匙”访问属性,无需明确调用方法。
KVC 是基于 NSObject 的一个扩展类别 —— NSKeyValueCoding 实现的。
因此,只要类继承自 NSObject,就可以天然支持 KVC。
四个常用方法
方法 | 作用 |
---|---|
setValue:forKey: | 通过 key 给属性赋值 |
setValue:forKeyPath: | 通过 keyPath给属性赋值 |
valueForKey: | 通过 key 获取属性 |
valueForKeyPath: | 通过 keyPath 获取属性 |
key 与 keyPath 区别
// key:只能访问当前对象的属性
[coffee setValue:@"100" forKey:@"price"];// keyPath:可访问关系链属性(例如嵌套对象)
[coffee setValue:@"white" forKeyPath:@"sonOfCoffee.color"];
keyPath 用点号(.)连接多个层级,是对嵌套对象的访问。
KVC 查找与赋值流程
KVC 的底层逻辑非常精细,赋值流程如下:
(1)查找 set 方法
按顺序查找以下方法:
- set:
- _set:
找到就直接调用
(2)未找到 set 方法时
会检查:
+ (BOOL)accessInstanceVariablesDirectly
如果返回 YES(默认值),则会依次查找成员变量:
_key → _isKey → key → isKey
找到后直接赋值。
(3)如果都没找到
调用:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
系统默认会抛异常,但你可以重写它,避免崩溃:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {NSLog(@"未定义的Key:%@", key);
}
(4)特殊情况:给基本类型赋 nil
若 int、float 等类型被赋 nil,系统会调用:
- (void)setNilValueForKey:(NSString *)key
可重写该方法来安全处理:
- (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString:@"age"]) age = 0;
}
KVC 支持数值与结构体类型
KVC 的 valueForKey: 总是返回 id 类型的对象。
如果访问的是数值或结构体,系统会自动封装成:
- 数值类 → NSNumber
- 结构体类 → NSValue
例如:
NSNumber *num = [NSNumber numberWithInt:10];
CGPoint point = CGPointMake(54, 45);
NSValue *value = [NSValue valueWithCGPoint:point];
通过 @encode() 还能让自定义结构体被封装:
NSValue *v = [NSValue valueWithBytes:&point objCType:@encode(CGPoint)];
KVC 验证机制
KVC 还支持验证赋值是否合法:
- (BOOL)validateValue:(inout id *)ioValueforKey:(NSString *)inKeyerror:(NSError **)outError {if ([inKey isEqualToString:@"name"]) {NSString *name = *ioValue;if (name.length == 0) {*outError = [NSError errorWithDomain:@"MyDomain"code:1001userInfo:@{NSLocalizedDescriptionKey:@"名字不能为空"}];return NO;}}return YES;
}
KVC 对集合的强大支持
KVC 内置了许多集合运算符,可以直接在数组上操作对象属性。
基本运算符:
运算符 | 含义 |
---|---|
@count | 元素数量 |
@sum | 求和 |
@avg | 平均值 |
@max | 最大值 |
@min | 最小值 |
NSNumber *avg = [array valueForKeyPath:@"@avg.age"];
NSNumber *sum = [array valueForKeyPath:@"@sum.age"];
对象运算符:
运算符 | 作用 |
---|---|
@unionOfObjects | 不去重 |
@distinctUnionOfObjects | 去重 |
@unionOfArrays | 二维数组不去重 |
@distinctUnionOfArrays | 二维数组去重 |
示例:
NSArray *names = [array valueForKeyPath:@"@unionOfObjects.name"];
KVC 操作字典
KVC 可以轻松在对象和字典之间转换:
// 取值为字典
NSDictionary *dict = [user dictionaryWithValuesForKeys:@[@"name", @"age"]];// 用字典批量赋值
[user setValuesForKeysWithDictionary:@{@"name":@"Tom", @"age":@18}];
这在 Model <-> JSON 转换 中极为常见。
KVC 的典型应用场景
场景 | 说明 |
---|---|
动态访问属性 | 运行时通过字符串操作属性 |
访问私有变量 | 可用于调试或框架内部处理 |
Model-字典转换 | 封装与解析 JSON |
修改 UI 控件内部属性 | 如 setValue:forKey:@“_placeholderLabel.textColor” |
集合操作 | 对模型数组进行聚合运算 |
二、KVO(Key-Value Observing)键值观察
概念回顾
KVO 是一种机制,让一个对象能在另一个对象属性变化时收到通知。
它常用于:
- Model → Controller 数据同步;
- Controller → View 自动更新;
- Model 自身监听依赖属性变化。
实现步骤
// 注册观察
[account addObserver:selfforKeyPath:@"balance"options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOldcontext:nil];// 回调方法
- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context {NSLog(@"余额变化:%@", change);
}// 移除观察
[account removeObserver:self forKeyPath:@"balance"];
监听机制(内部原理)
官方文档中的解释:NSObject 在运行时会动态创建一个隐藏的子类,并重写被观察属性的 setter 方法来插入通知逻辑。
流程如下:
- 系统创建类:NSKVONotifying_Account
- 替换对象的 isa 指针
- 重写 setter:
willChangeValueForKey:
[super setBalance:newValue];
didChangeValueForKey:
- 调用观察者回调
KVO 与通知的区别
对比项 | KVO | NSNotification |
---|---|---|
触发时机 | 属性值改变 | 手动发布 |
是否自动触发 | ✅ | ❌ |
通知范围 | 一对一 | 一对多(全局) |
基础实现 | 依赖 KVC | 独立机制 |
用途 | 监听对象属性 | 广播事件消息 |
KVO 的优缺点
优点 | 缺点 |
---|---|
系统内置,几乎零代码 | 语法繁琐 |
自动通知机制 | 忘记移除崩溃 |
可观察依赖属性 | 可读性差 |
可用于数据绑定 | 难调试 |
三、KVC 与 KVO 的关系
可以这样理解:
对比 | KVC | KVO |
---|---|---|
作用 | 动态访问属性 | 动态监听属性 |
触发方式 | 调用 setter/getter | setter 内部触发通知 |
实现依赖 | NSObject(NSKeyValueCoding) | NSObject(NSKeyValueObserving) |
内部关系 | 提供属性访问通道 | 基于 KVC 的通道检测变化 |
KVO 是建立在 KVC 基础上的,所以也可以说没有 KVC,就没有 KVO。
四、总结
概念 | 特点 | 典型用途 |
---|---|---|
KVC | 用字符串访问属性,支持私有变量和集合操作 | JSON 转 Model、调试 |
KVO | 自动监听属性变化并触发通知 | 数据绑定、界面更新 |
联系 | KVO 内部依赖 KVC 的访问机制 | —— |
最后一句话概述就是,KVC 打开属性的大门,而当属性被改变时,KVO 会自动提醒我们