三十一、KVO(简单应用)

本文由快学吧个人写作,以任何形式转载请表明原文出处。

一、资料准备

KVO官方文档

二、基本信息

要了解KVO,必须了解KVC,KVO是建立在KVC的基础上的一套事件通知机制。

英文全称 : Key-Value Observing

中文全称 : 键值观察

作用 : 添加KVO观察的对象的属性发生更改的时候,会发出通知。

使用范围 : 继承于NSObject的类。

KVO经常和NSNotificatioCenter做比较,简单说下 :

相同点 :

  1. 都是观察者,都可以监听事件。
  2. 对于通知来说,都是可以一对多的通知。

不同点 :

  1. KVO监听的是对象的属性(不是成员变量),具体来说就是遵循KVC规则的对象属性。NSNotificatioCenter监听的范围更广。
  2. KVO可以记录属性的旧值和新值的变化。
  3. 对于被观察者和观察者来说,KVO是1对1的,NSNotificatioCenter是1对多的。
  4. KVO使用后必须销毁,不然还会继续发送通知,就可能会出现野指针的情况。NSNotificatioCenter则在iOS9以后就不会向已经销毁的监听器发送通知了,也不会对已经销毁的被监听对象发送消息。所以就不会出现野指针。

三、KVO使用

1. 基本使用

  1. 注册观察者
  2. 响应回调函数
  3. 移除观察者

1. 注册观察者

objectivec 复制代码
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

例如 :

objectivec 复制代码
// 假设self 是一个vc,拥有属性man
[self.man addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

这就是说给self.man添加一个观察者是self,观察的是self.man的name属性。

参数 :

  1. 调用者(就是上面的self.man)就是被观察者。
  2. observer就是观察者
  3. keyPath就是要观察被观察者的哪个属性。
  4. options是观察策略.
  5. context是通过回调方法传递给观察者的任意数据。

对于options :

是个枚举值,有4个枚举元素 :

  1. NSKeyValueObservingOptionNew : 在回调方法里提供更改前的值
  2. NSKeyValueObservingOptionOld : 在回调方法理提供更改后的值
  3. NSKeyValueObservingOptionInitial : 观察最初的值,使用这个的话会在注册通知的时候(也就是上面的addObserver)就调用一次回调方法。
  4. NSKeyValueObservingOptionPrior : 在值修改前和修改后都调用回调方法。也就是调用两次回调方法。

对于context :

本身是一个函数指针类型,主要是用来区分被多个观察者的相同属性名,比如JDMan有name属性,JDKid也有name属性,都找了同一个观察者,用context来区分JDMan和JDKid的name属性,而不用if嵌套,可以使预编译和编译期的效率更高。

2. 回调方法

objectivec 复制代码
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context

观察者必须实现回调方法,通知也是从这里拿到的。一旦观察的属性发生了变化,就会进入到这个回调方法。

举例 :

less 复制代码
#import "ViewController.h"
#import "JDMan.h"

static void*ManNameContext = &ManNameContext;

@interface ViewController ()

@property (nonatomic,strong) JDMan *man;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.man = [[JDMan alloc] init];
    [self.man addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:ManNameContext];
    // Do any additional setup after loading the view.
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == ManNameContext) {
        NSLog(@"这是JDMan的name");
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

@end

参数 :

  1. keyPath : 被观察者的属性
  2. object : 被观察者
  3. change : 发生的变化
  4. context : 就是注册时候传递过来的,用来高效区分是哪个被观察者的属性。

3. 移除观察者

objectivec 复制代码
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

举例 :

在上面举例的VC中实现dealloc

objectivec 复制代码
- (void)dealloc
{
    [self.man removeObserver:self forKeyPath:@"name"];
}

良好的习惯就是注册了就要记得在dealloc中移除。不然可能会出现野指针的问题。

2. 手动与自动发送通知

objectivec 复制代码
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

要添加在被观察者的实现中。key就是被观察者的属性名称。默认返回时YES,也就是默认情况下,只要key这个名称的属性值发生了改变,就会自动调用回调函数。

如果给它返回值重写成NO。则不会调用回调函数。也就是不会发送通知。

例如 :

JDMan :

注意右边的return NO。

VC :

运行,点击button给self.man.name修改值,是不会调用回调方法的。设置成YES就可以了。如果只想监听某个属性,并且有时想监听,有时候又不想,那么就设置成NO。然后在需要监听的属性的setter方法里面加入如下代码 :

objectivec 复制代码
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

再运行的时候,就会监听name的改变了。不需要回调的时候,直接去掉这两个方法就行。

3. 属性存在依赖

某一个属性的改变需要依赖其他的属性。

举例如下 :

三个属性存在关系,当haveDoneThings或者allThings发生改变的时候,doneRate也要发生改变,存在的关系是: haveDoneThings/allThings = doneRate

需要被观察的是doneRate,所以给doneRate添加观察者为VC。并要在doneRate的getter方法里面写明其关系 : haveDoneThings/allThings = doneRate

为了实时观察的到haveDoneThingsallThings改变后doneRate的值。需要为doneRate在类的实现文件中实现haveDoneThingsallThingsdoneRate的关联。

JDMan.m代码如下 :

VC代码如下 :

运行结果 :

不断的点击button,会看到doneRate的变化 :

4. 观察可变数组

要注意的是给数组添加或者删除元素不要用[arr addObject:]这一类的方法,这种方法不是KVC的键值设置方式,所以不会走KVC的设值。而KVO是基于KVC进行观察的,所以是不能观察到可变数组通过原始方式添加或者删除值的。如下,是不会有观察回调的。

JDMan :

VC :

css 复制代码
[[self.man mutableArrayValueForKey:@"mutArr"] addObject:@"1"];

设置可变数组的值,可以进行回调处理。

对于控制台输出的kind = 2 :

kind是NSKeyValueChange枚举值 :

objectivec 复制代码
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,     //设置值
    NSKeyValueChangeInsertion = 2,   //插入值
    NSKeyValueChangeRemoval = 3,     //移除值
    NSKeyValueChangeReplacement = 4, //替换值
};
相关推荐
Unlimitedz7 小时前
iOS内存管理中的强引用问题
macos·ios·cocoa
雨夜赶路人8 小时前
iOS开发--接入ADMob广告失败
ios
旭日猎鹰9 小时前
iOS崩溃堆栈分析
ios
SY.ZHOU9 小时前
Flutter 与原生通信
android·flutter·ios
鸿蒙布道师12 小时前
鸿蒙NEXT开发文件预览工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师12 小时前
鸿蒙NEXT开发全局上下文管理类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
归辞...13 小时前
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)
笔记·ios·cocoa
码客前端14 小时前
ios接入穿山甲【Swift】
macos·ios·cocoa
键盘敲没电14 小时前
【iOS】UITableView性能优化
ios·性能优化·ipad
星鹿XINGLOO15 小时前
ChatGPT语音功能在iPad上支持吗?全面解答!
人工智能·安全·ios·ai·chatgpt·语音识别·ipad