白水鉴心

iOS 模式详解—「KVC编码 & KVO监听」

引导


开发过程中,最常见的就是程序的流程取决于你所使用的各种变量和属性的值,根据变量和属性的值确定后面运行的代码。
学好「获取类中属性的变化」这一模块是开发重要部分之一,
目地:为了解决在开发过程中,由需求改变引发的各种蛋疼、繁琐的问题。

本篇文章主要从【KVC & KVO 使用】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@白开水ln原著;

目录:

  1. KVC 释义
  2. KVC 常用方法
  3. KVC 实现原理
  4. KVC 使用
    1.KVC 赋值 & 取值
    2.KVC 访问私有成员变量
    3.KVC 字典转模型
  5. KVO 释义
  6. KVO 使用
  7. KVO 实现原理
  8. KVO 手动发送通知机制
  9. SourceCodeToolsClassWechatPublic-Codeidea

我们有多种方式获取对象的改变。例如,使用委托、通知获取值的改变。如果需要观察多个属性的变化,使用委托或通知会产生大量代码,一个更好用来观察属性变化的方法是使用 键值监听(KeyValueObserving,简称KVO),Apple 在自己的软件中大量使用 KVO。使用 KVO 跟踪单个属性或集合(如数组)的变化非常高效,键值观察建立在 键值编码(KeyValueCoding,简称KVC) 基础上,也就是任何你想使用 KVO 监听的属性必须符合键值编码。KVO 只需要在观察者方法中添加代码,不需要修改被观察文件内代码,这一点和委托、通知不同。

KVC 释义


KVC(全称 keyvaluecoding)即键值编码。KVC 的操作方法由NSKeyValueCoding 非正式协议提供,而NSObject(NSKeyValueCoding)就实现了这个协议,也就是说ObjC中几乎所有的对象都支持 KVC 操作,它是一种不通过存取方法(Setter、Getter),而通过属性名称字符串(key)间接访问类属性(实例变量)的机制。

KVC 常用方法

KVC 常用的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1.赋值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
2.获取值
- (id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
注解:
1.forkey: 用于简单赋值取值
2.forKeyPath: 用于复合属性,进行内部的点语法,层层访问内部的属性; 例如 forKeyPath:@"dog.money",Student学生模型类中包含Dog类,Dog类中有的money属性。
3.区别:
1、`forKeyPath` 包含了所有 `forKey` 的功能
2、`forKeyPath` 进行内部的点语法,层层访问内部的属性
3、注意:`key` 值一定要在属性中找到,开发中最好使用`forKeyPath`。

KVC 实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、[item setValue:@"白开水ln简书" forKey:@"name"];
1.首先去模型中查找该对应的key值有没有set方法,若有就会自动调用set方法进行赋值 [self setName:@"白开水ln"]。
2.如果没有set方法,那么它就判断有没有key相同名称并且带有下划线的成员变量,如果就就给该属性赋值 _name = value.
3.如果没有带有下划线的成员变量,那么它就会查看有没有跟key值相同名称的属性,如果有就给该属性赋值 name = value.
4.如果还找不到,就会直接报找不到的错误 ('setValue:forUndefinedKey`).
2、[item setValuesForKeysWithDictionary:dict];
1.遍历字典中所有key
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
2.去模型中查找有没有对应属性
[item setValue:value forKey:key];
}];

KVC 基本使用


KVC 赋值 & 取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Student *stu = [[Student alloc] init];
_stu = stu;
stu.dog = [[Dog alloc] init];
// 1.【验证赋值】KVC 自动类型转换 (Student -> Dog -> float money属性)
[stu.dog setValue:@11.0 forKey:@"money"];
NSLog(@"%.2f",stu.dog.money);
[stu setValue:@"25" forKeyPath:@"dog.money"]; // 字符串
NSLog(@"%.2f",stu.dog.money);
[stu setValue:[NSNumber numberWithInteger:30] forKeyPath:@"dog.money"]; // NSNumber
NSLog(@"%.2f",stu.dog.money);
KVC 对多种数据类型的支持
打印输出会自动转换成 float 类型 11.00, 25.00, 30.00;
- - -
// 2.KVC 取值
[_stu.dog valueForKey:@"nameDog"];
[_stu valueForKeyPath:@"dog.nameDog"];
KVC 访问私有成员变量
1
2
3
4
5
6
7
8
9
10
11
@interface Dog ()
{
int _dogAge;
}
// 3.访问私有成员变量
[stu.dog setValue:@6 forKeyPath:@"dogAge"];
NSLog(@"%@", [_stu.dog valueForKeyPath:@"dogAge"]);
注解:上面的 `keyPath` 写dogAge 或 _dogAge都可以,KVC 会自动去查找。
KVC 字典转模型

简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NSDictionary *dictMode = @{
@"name" :@"WeCaht-Codeidea",
@"age" : @25,
@"coder" : @"iOS" // -->问题1:模型的属性和字典不能一一对应
/*
@"dog" : @{
@"nameDog" : @"wangcai",
@"money" : @8
},
*/ //-->问题2:模型中嵌套模型
};
Student *student = [[Student alloc] init];
student.dog = [[Dog alloc] init];
// 1.字典转模型
[student setValuesForKeysWithDictionary:dictMode];
NSLog(@"%@ ,%ld",student.name,student.age);
// 2.模型转字典
NSDictionary *modeDict = [student dictionaryWithValuesForKeys:@[@"name", @"age"]];
NSLog(@"%@",modeDict);
KVCKVO[3746:82557] WeCaht-Codeidea ,25
KVCKVO[3746:82557] {name:WeCaht-Codeidea,age:25}

注解:
1、开发中不建议使用 setValuesForKeysWithDictionary:(把字典中所有值给模型的属性赋值)。
2、 问题1: 模型的属性和字典不能一一对应,会报以下错误
reason: '[<Student 0x600000230220> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key coder.'
解决: 重写系统方法
-(void)setValue:(id)value forUndefinedKey:(NSString *)key

补充: 什么时候重写系统方法
1.想给系统方法添加额外功能;2.不想要系统方法实现

3、问题2: 模型中嵌套模型
如果模型中带有模型型,setValuesForKeysWithDictionary 不能用。

解决:思路 - 拿到每一个模型属性,去字典中取出对应的值,给模型赋值(提醒:从字典中取值,不一定要全部取出来)。
建议使用:MJExtension 字典转模型 和 Runtime(根据模型中属性,去字典中取出对应的 value 给模型属性赋值)

KVO 释义

KVOKeyValueObersver)即键值监听,利用一个key来找到某个属性并监听其属性值得改变,当该属性发生变化时,会自动的通知观察者,这比通知中心需要post通知来说,简单了许多。其实这也是一种典型的观察者模式。

KVO 使用


  1. 给目标对象的属性添加观察者
  2. 在回调方法中监听属性的变化
  3. 移除观察者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// KVO 键值监听
- (void)setupKVO
{
Student *stu = [[Student alloc] init];
_stu = stu;
stu.name = @"简书白开水ln";
// 1.添加观察者 (给目标对象)
// options 选项(可选属性值,示例:旧值和新值)
[stu addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
stu.name = @"我是KVO键值监听重新赋值";
stu.name = @"二次重新赋值";
}
/**
* 2.在回调方法中监听属性的变化
* @param keyPath 要监听的属性
* @param object 要监听的属性所属的对象
* @param change 改变的内容
* @param context 上下文
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"%@ - %@", keyPath, change);
}
- (void)dealloc
{ // 3.移除观察者
[self.stu removeObserver:self forKeyPath:@"name"];
}

KVO 实现原理


当一个类的属性被观察的时候,系统会通过runtime动态的创建一个该类的派生类,并且会在这个类中重写基类被观察的属性的setter方法,而且系统将这个类的isa指针指向了派生类,从而实现了给监听的属性赋值时调用的是派生类的setter方法。重写的setter方法会在调用原setter方法前后,通知观察对象值得改变。此外,派生类还重写了 dealloc 方法来释放资源。

KVO 实现原理

可以看到重写的 setter 方法,给属性赋值的前后分别调用了两个方法。

1
2
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

- (void)didChangeValueForKey:(NSString *)key;会调用

1
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSString*, id> *)change context:(nullable void *)context;

KVO 手动发送通知机制


默认情况下,KVO 观察到属性变化系统会自动发送通知,但在某些情况下,你可能需要控制何时发送通知。例如:在某些情况下不需要发送通知,或将多个改变合并为一个通知发送。其实我们也可以手动,显式的调用上面两个方法,以使其具有通知机制。
举个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)setupKVO1
{
Student *student = [[Student alloc] init];
student.name = @"简书白开水ln";
// 1.添加观察者
[student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
// 直接修改成员变量,手动的调用上下两个方法,使其具有通知机制
[self willChangeValueForKey:@"name"];
student.name = @"KVO 手动发送通知机制";
[self didChangeValueForKey:@"name"];
}
// 2.在回调方法中监听属性的变化
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(Student *)student change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"name"]) {
NSLog(@"%@ - %@", keyPath, change);
} else {
[super observeValueForKeyPath:keyPath ofObject:student change:change context:context];
}
}

Reading


  • 如果在阅读过程中遇到 error || new ideas,希望你能 issue 我,我会及时补充谢谢。
  • 喜欢可 赞赏 or Star 一波;点击左上角关注 或『微众 Codeidea』,在 Demo 更新时收到邮件通知,便捷你的阅读。
🖋 Plain boiled water ln : 本文结束    感谢阅读 ^_^. Need coffee?
👁 At this time suggest 1 minute eye exercises

本文标题:iOS 模式详解—「KVC编码 & KVO监听」

文章作者:白开水ln

原始链接:http://plainboiledwaterln.cn/iOSUI/KVCKVO.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 『微众圈:Codeidea』本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和作者出处,谢谢合作!

「喜欢就留言or赞赏」but「支持不要超过你早餐费的 0.5」 ^_^.