神刀安全网

实用详解「KVC编码 & KVO监听」


Write in the first【写在最前】


开发过程中,最常见的就是程序的流程取决于你所使用的各种变量和属性的值,根据变量和属性的值确定后面运行的代码。

学好「获取类中属性的变化」这一模块是开发重要部分之一,
目地:为了解决在开发过程中,由需求改变引发的各种蛋疼、繁琐的问题。

本篇文章主要从【KVC & KVO 实战开发使用场景相关】学习总结。
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,以提高文章质量。

实用详解「KVC编码 & KVO监听」

KVC & KVO

目录:

  1. 引导
  2. KVC 概论
  3. KVC 常用方法
  4. KVC 对多种数据类型的支持
  5. KVC 实现原理
  6. KVC 基本使用
    1.KVC 简单赋值 & 取值
    2.KVC 访问私有成员变量
    3.KVC 字典转模型
  7. KVO 概论
  8. KVO 使用步骤
  9. KVO 实现原理
  10. KVO 手动发送通知机制

引导


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

KVC 和 KVO 提供了一个强大高效的方式来编写代码,学习 KVO 前必须先掌握 KVC,所以 我们按实用开发技巧一点点剖析它。

KVC 概论


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

KVC 常用方法


KVC 常用的方法如下:

  • 赋值
- (void)setValue:(nullable id)value forKey:(NSString *)key; - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  // 注解: setValue:(属性值) forKey:(属性名),(用于简单属性)。 setValue:(属性值) forKeyPath:(属性名),(用于复合属性,进行内部的点语法,层层访问内部的属性; 例如student.name,Student学生模型类中的name属性。
  • 获取值
- (id)valueForKey:(NSString *)key; - (nullable id)valueForKeyPath:(NSString *)keyPath;  // 注解: valueForKey:属性名 valueForKeyPath:属性名(用于复合属性)

KVC 对多种数据类型的支持


首先要说的是对于基本数据类型的属性,KVC 的这几个方法会自动装箱和拆箱。其次,KVC 也支持数组和字典等集合数据。这里了解不多,不做过多总结,有兴趣可参考:KVC/KVO原理详解及编程指南

简单示例:KVC 自动类型转换
如:模型类定义的属性是 float money

@property (nonatomic, assign) float money;  // KVC 赋值 [person setValue:@"18" forKeyPath:@"money"]; [person setValue:[NSNumber numberWithInteger:18] forKeyPath:@"money"]; 打印输出会自动转换成 float 类型 18.00;

KVC 实现原理


1、[item setValue:@"白开水ln简书" forKey:@"name"];

  • 1.首先去模型中查找有没有 setName,若有,直接调用赋值 [self setName:@"白开水ln简书"]
  • 2.若无,去模型中查找有没有 name 属性,若有,直接访问属性赋值 name = value
  • 3.若无,再去模型中查找有没有 _name 成员变量,若有,直接访问属性赋值 _name = value
  • 4.找不到,就会直接报找不到的错误(valueForUndefinedKey:)。

2、[item setValuesForKeysWithDictionary:dict];

  • 1.遍历字典中所有 key

  • 2.去模型中查找有没有对应的属性。

    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {   // 2.去模型中查找有没有对应属性 KVC   [item setValue:value forKey:key]; }];

KVC 基本使用


KVC 简单赋值 & 取值
LNPerson *person = [[LNPerson alloc] init];     person.dog = [[LNDog alloc] init];  [person.dog setValue:@"阿黄" forKey:@"name"]; [person setValue:@"旺财" forKeyPath:@"dog.name"]; NSLog(@"%@", person.dog.name);

区别:forKey:forKeyPath:
1、forKeyPath 包含了所有 forKey 的功能
2、forKeyPath 进行内部的点语法,层层访问内部的属性
3、注意:key 值一定要在属性中找到,开发中最好使用forKeyPath

KVC 取值

[person valueForKeyPath:@"name"]  // 取出数组中所有模型的某个属性值 NSArray *allPersons = @[person1, person2, person3]; NSArray *allPersonName = [allPersons valueForKeyPath:@"name"];
KVC 访问私有成员变量
@implementation LNPerson {       int _age;  }  LNPerson *person = [[LNPerson alloc] init]; [person setValue:@"88" forKeyPath:@"age"];

注意:上面的 keyPath 写age 或 _age都可以,KVC 会自动去查找。

KVC 字典转模型

简单示例:

NSDictionary *dict = @{                      @"name" :@"lurry",                      @"money" : @189.88,                      //@"development" : @"iOS"-->问题1模型的属性和字典不能一一对应                      /*                       @"dog" : @{                                @"name" : @"wangcai",                                @"price" : @8                                },                       */ //-->问题2模型中嵌套模型                        }; LNPerson *person = [[LNPerson alloc] init]; [person setValuesForKeysWithDictionary:dict];// 等同于下面的代码  // setValuesForKeysWithDictionary: 原理: // 1.遍历字典中所有key,去模型中查找有没有对应的属性 [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {      // 2.去模型中查找有没有对应属性 KVC     [item setValue:value forKey:key];       }];  NSLog(@"%@", person);
  • 开发中不建议使用 setValuesForKeysWithDictionary:(把字典中所有值给模型的属性赋值)

  • 问题1:如果服务器多返回几个数据 Key,在模型中系统找不到就会报错。

    reason: '[<LNPerson 0x100304730> valueForUndefinedKey:]: this class is not key value coding-compliant for the key nam.

    解决:重写系统方法 setValue:forUndefinedKey:,就不会有报错信息了。
    补充:什么时候重写系统方法?
    1、想给系统方法添加额外功能 2、不想要系统方法实现

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

  • 模型转成字典

    NSDictionary *dict = [person dictionaryWithValuesForKeys:@[@"name", @"money"]];

KVO 概论


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

KVO 使用步骤


  1. 给目标对象的属性添加观察者
  2. 在回调方法中监听属性的变化
  3. 移除观察者

具体代码如下:

- (void)viewDidLoad {     [super viewDidLoad];     self.person = [[LNPerson alloc] init];     person.name = @"zs";     /*      - Observer 观察者      - KeyPath 要监听的属性      - options 选项(可选属性值,示例:旧值和新值)     */     // 1.添加观察者     [self.person addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];     person.name = @"ls";// 这里重新赋值     person.name = @"ww"; } - (void)dealloc{     // 2.移除观察者     [self.person removeObserver:self forKeyPath:@"name"]; }  /**   *  当监听的属性值发生改变  *  @param keyPath 要监听的属性  *  @param object  要监听的属性所属的对象  *  @param change  改变的内容  *  @param context 上下文  */ #pragma mark - KVO // 3.监听属性的变化 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(LNPerson *)person change:(NSDictionary<NSString *,id> *)change context:(void *)context{     NSLog(@"%@------%@------%@", keyPath, change); }

KVO 实现原理


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

实用详解「KVC编码 &amp; KVO监听」

KVO 实现原理

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

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

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

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

KVO 手动发送通知机制


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

- (void)viewDidLoad {     [super viewDidLoad];     self.person = [[LNPerson alloc] init];     person.name = @"zs";      // 1.添加观察者     [self.person addObserver:self forKeyPath:@"name" options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];      [self willChangeValueForKey:@"name"];     person.name = @"this is a test"; // 直接修改成员变量的值,手动的调用上下两个方法,使其就有通知机制     [self didChangeValueForKey:@"name"]; } - (void)dealloc{     // 2.移除观察者     [self.person removeObserver:self forKeyPath:@"name"]; }  #pragma mark - KVO // 3.监听属性的变化 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(LNPerson *)person change:(NSDictionary<NSString *,id> *)change context:(void *)context{     if(object == self && [keyPath isEqualToString:@"name"]) {         NSLog(@"%@------%@------%@", keyPath, change);     } else {         [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];     }}

期待


  • 如果在阅读过程中遇到 error,希望你能 Issues 我,谢谢。

  • 如果你想为【本文相关】分享点什么,也希望你能 Issues 我,我非常想为这篇文章增加更多实用的内容,谢谢。

  • 「博客原文」,对本文我会【不定时、持续更新、一些 学习心得与文章、实用才是硬道理】^_^.

Subsequent【后续】


【我也是对所花费时间的一个总结】
学习总结–> GitHub(现在代码少点,总结好 待上传)、白开水Blog白开水ln-简书
一劳永逸–>

我只是个【有思想的代码搬运工】加上【自己的学习总结】写出来的文章。

如果「你喜欢 或 有帮助」,可否点个 Star

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 实用详解「KVC编码 & KVO监听」

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址