三十一、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, //替换值
};
相关推荐
B.-3 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
iFlyCai13 小时前
Xcode 16 pod init失败的解决方案
ios·xcode·swift
郝晨妤1 天前
HarmonyOS和OpenHarmony区别是什么?鸿蒙和安卓IOS的区别是什么?
android·ios·harmonyos·鸿蒙
Hgc558886661 天前
iOS 18.1,未公开的新功能
ios
CocoaKier1 天前
苹果商店下载链接如何获取
ios·apple
zhlx28351 天前
【免越狱】iOS砸壳 可下载AppStore任意版本 旧版本IPA下载
macos·ios·cocoa
XZHOUMIN2 天前
网易博客旧文----编译用于IOS的zlib版本
ios
爱吃香菇的小白菜2 天前
H5跳转App 判断App是否安装
前端·ios
二流小码农2 天前
鸿蒙开发:ForEach中为什么键值生成函数很重要
android·ios·harmonyos
hxx2212 天前
iOS swift开发--- 加载PDF文件并显示内容
ios·pdf·swift