【iOS】KVC KVO 总结

文章目录

  • KVC
    • 1. KVC赋值原理 setValue:forKey:
    • 2. KVC取值原理 valueForKey:
    • 3. 注意
    • 4. KVC的批量存值和取值
  • KVO 使用
    • 1. KVO的介绍
    • 2. KVO监听的步骤
      • 注册监听
      • 监听实现
      • 移除监听
      • 例子
    • 3. KVO的传值
    • 4. KVO注意
    • 5. KVO的使用场景
  • KVO原理
    • 1. KVO的本质是改变了setter方法的调用
    • 2. _NSSetXXXValueAndNotify的内部实现原理
    • 3. _NSSetObjectValueAndNotify
    • 4. 手动调用KVO
    • 5. KVO的实现的注意
    • 6. KVO类指针交换
  • KVO总结
    • 1. KVO的本质
    • 2. KVO实现过程总结
  • KVO & KVC 问题总结
      • 3.1 isa混写之后如何调用方法?
      • 3.2 为什么在生成的子类内部重写class方法
      • 3.3 直接修改成员变量的值,会不会触发KVO?
      • 3.4 KVC修改属性会触发KVO吗?
      • 3.5 KVO怎么监听数组的元素变化?

KVC

1. KVC赋值原理 setValue:forKey:

  • 首先会按照setKey_setKey的顺序查找方法,找到方法,直接调用方法并赋值;
  • 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES);
  • accessInstanceVariablesDirectly方法返回YES,则按照_key_isKeykeyisKey的顺序查找成员变量,找到直接赋值,找不到则抛出NSUnknowKeyExpection异常;
  • accessInstanceVariablesDirectly方法返回NO,那么就会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常;
    在这里插入图片描述

2. KVC取值原理 valueForKey:

  1. 首先会按照getKeykeyisKey_key的顺序查找方法,找到直接调用取值
  2. 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常;
  3. 若返回的YES,则按照_key_isKeykeyisKey的顺序查找成员变量,找到则取值;
  4. 找不到则调用valueForUndefinedKey:抛出NSUnknowKeyExpection异常;
    在这里插入图片描述

3. 注意

  1. key的值必须正确,如果拼写错误,会出现异常。
  2. key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果你自己写了这个方法,key的值出错就会调用到这里来。
  3. 因为类可以反复嵌套,所以有个keyPath的概念,keyPath就是用.号来把一个一个key链接起来,这样就可以根据这个路径访问下去。
  4. NSArrayNSSet等都支持KVC
  5. 可以通过KVC访问自定义类型的私有成员。
  6. 如果对非对象传递一个nil值,KVC会调用setNIlValueForKey方法,我们可以重写这个方法来避免传递nil出现的错误,对象并不会调用这个方法,而是会直接报错。
  7. 处理非对象,setValue时,如果要赋值的对象是基本类型,需要将值封装成NSNumber或者NSValue类型,valueForKey时,返回的是id类型的对象,基本数据类型也会被封装成NSNumber或者NSValuevalueForKey可以自动将值封装成对象,但是setValue:forKey:却不行。我们必须手动讲值类型转换成NSNumber/NSValue类型才能进行传递initWithBool:(BOOL)value。

4. KVC的批量存值和取值


@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *sex;
@property (nonatomic, strong) NSString *age;
@end

// KVC批量赋值
- (void)kvc_setKeys {
    NSDictionary *kvc_dict = @{@"name": @"clearlove", @"sex": @"male", @"pr_age": @"21"};
    Person *pr = [[Person alloc] init];
    [pr setValuesForKeysWithDictionary:kvc_dict];
    NSLog(@"%@", pr.age);
}

// KVC批量取值
- (void)kvc_getValues {
    Person *pr = [[Person alloc] init];
    [pr setValue:@"mekio" forKey:@"name"];
    [pr setValue:@"male" forKey:@"sex"];
    [pr setValue:@"120" forKey:@"pr_age"];
    NSDictionary *pr_dict = [pr dictionaryWithValuesForKeys:@[@"name", @"age", @"sex"]];
    NSLog(@"%@", pr_dict);
}

如果有取值或者赋值的时候有key和属性不对应,重写- (void)setValue:(id)value forUndefinedKey:(NSString *)key 方法
(上面代码是已经重写的部分

请添加图片描述

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    if ([key isEqualToString:@"pr_age"]) {
        self.age = (NSString *)value;
    }
}

KVO 使用

1. KVO的介绍

KVO的全称是Key Value Observing: 键值监听,可以用于监听某个对象属性值的改变;

KVO时苹果提供的一套事件通知机制, KVONSNotificationCenter都是iOS观察者模式的实现。

区别: NSNotificaionCenter可以存在一对多,而KVO则是 一对一的 关系。

2. KVO监听的步骤

注册监听

通过[addObserver:forKeyPath:options:context:]方法注册KVO,这样可以接收到keyPath属性的变化事件;

observer:观察者,监听属性变化的对象。该对象必须实现observeValueForKeyPath:ofObject:change:context: 方法。
keyPath:要观察的属性名称。要和属性声明的名称一致。
options:回调方法中收到被观察者的属性的旧值或新值等,对KVO机制进行配置,修改KVO通知的时机以及通知的内容
context:传入任意类型的对象,在"接收消息回调"的代码中可以接收到这个对象,是KVO中的一种传值方式。

监听实现

通过方法[observeValueForKeyPath:ofObject:change:context:]实现KVO的监听;

keyPath:被观察对象的属性
object:被观察的对象
change:字典,存放相关的值,根据options传入的枚举来返回新值旧值
context:注册观察者的时候,context传递过来的值

移除监听

在不需要监听的时候,通过方法[removeObserver:forKeyPath:],移除监听;

例子

  • 在view实现一个button监听person的name变化。
// kvo监听
- (void)kvo_pr {
    
    NSDictionary *kvc_dict = @{@"name": @"clearlove", @"sex": @"male", @"pr_age": @"21"};
    self.pr_1 = [[Person alloc] init];
    [self.pr_1 setValuesForKeysWithDictionary:kvc_dict];
    
    [self.pr_1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"new_name == %@", [change valueForKey:@"new"]);
}
- (void)dealloc {
    [self.pr_1 removeObserver:self forKeyPath:@"name"];
}
  • 如果想控制当前对象的自动调用过程,也就是由上面两个方法发起的KVO调用,则可以重写下面方法。方法返回YES则表示可以调用相关对象的监听事件,如果返回NO则表示不可以调用相关对象的监听事件。
  • 如果name不可以被监听,在persn的实现重写(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key 方法
// 禁止监听某个属性
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        NSLog(@"forbid Nostifity pr.name");
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey: key];
}

3. KVO的传值

  • 可以通过方法context传入任意类型的对象,在接收消息回调的代码中可以接收到这个对象,是KVO中的一种传值方式。
  • 通过KVO在ModelController之间进行通信。

4. KVO注意

  • 调用[removeObserver:forKeyPath:]需要在观察者消失之前,否则会导致Crash。
  • 在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash。
  • 观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。
  • KVO的addObserverremoveObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。

5. KVO的使用场景

  1. 对于时刻变化的对象,例如colletionViewitems,总是动态的变化,这个时候可以使用KVO监听对象。
  2. AVFounditon中获取AVPlayer的播放进度,播放状态,也需要使用KVO来观察。

KVO原理

1. KVO的本质是改变了setter方法的调用

  1. 利用Runtime API动态生成一个子类NSKVONotifying_XXX,并且让instance对象的isa指向这个全新的子类,NSKVONotifying_XXXsuperclass指针指向原来的类;
  2. 当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数;

2. _NSSetXXXValueAndNotify的内部实现原理

- setName:最主要的重写方法,set值时调用通知函数
- class:返回原来类的class
- dealloc
- _isKVOA判断这个类有没有被KVO动态生成子类

- (void)setClassName:(NSString *)className {

}

- (Class)class {
- 这是为了保证该中间类在外部使用时可以替代原始类,实现完全透明的KVO功能。
    return [testClass class];
}

- (void)dealloc {
    // 收尾工作
}

- (BOOL)_isKVOA {
- 添加一个名为_isKVOA的实例变量**,用于标识该对象是否支持KVO机制。
    return YES;
}



重写Class方法 :这是为了保证该中间类在外部使用时可以替代原始类,实现完全透明的KVO功能。

添加一个名为_isKVOA的实例变量,用于标识该对象是否支持KVO机制。

3. _NSSetObjectValueAndNotify

在具体实现过程中,系统会动态生成一个继承自原始类的中间类_NSSetXXXValueAndNotify,并且在该类的初始化方法中,调用了一个叫做_NSSetObjectValueAndNotify()的函数,用于实现属性改变的通知。

_NSSetObjectValueAndNotify()函数的实现过程如下:

a) 首先会调用 willChangeValueForKey
b) 调用原来的setter实现然后给属性赋值
c) 最后调用 didChangeValueForKey
d) 最后调用 observerobserveValueForKeyPath 去告诉监听器属性值发生了改变 .

4. 手动调用KVO

  1. 修改类方法automaticallyNotifiesObserversForKey的返回值;
  2. 调用KVO主要依靠两个方法,在属性发生改变之前调用willChangeValueForKey方法,在发生改变之后调用didChangeValueForKey方法即可;

5. KVO的实现的注意

  1. 当观对象移除所有的监听后,会将观察对象的isa指向原来的类。
  2. 当观察对象的监听全部移除后,动态生成的类不会注销,而是留在下次观察的时候在用,避免反复创建中间子类。

6. KVO类指针交换

  1. isa-swizzling(类指针交换)就是把当前某个实例对象的isa指针指向一个新建造的中间类,在这个新建造的中间类上面做hook方法或者别的事情,这样不会影响这个类的其他实例对象,仅仅影响当前的实例对象。
    在这里插入图片描述

KVO总结

1. KVO的本质

  1. 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  2. 当修改instance对象的属性时,会调用Foundation_NSSetXXXValueAndNotify函数
  3. 接着调用父类原来的setter方法修改属性的值。
  4. 最后调用didChangeValueForKey,其内部会触发监听器(Oberser)的监听方法(observerValueForKeyPath:ofObject:change:context:);

2. KVO实现过程总结

  1. 调用 addObserver:forKeyPath:options:context:context调用的时候,会自动生成并注册一个该对象(被观察的对象)对应类的子类,取名NSKVONotify_Class,并且将该对象的isa指针指向这个新的类。
  2. 在该子类内部实现4个方法-被观察属性的set方法、class方法、isKVOdelloc
  3. 最关键的是set方法中,先调用willChangeValueForKey再调用原来的setter方法给成员变量赋值,最后调用didChangeValueForKey
  • willChangeValueForKey和didChangeValueForKey需要成对出现才能生效,在didChangeValueForKey中会去调用观察者的observeValueForKeyPath: ofObject: 方法。
  1. 重写class方法,这样避免外部感知子类的存在,同时防止在一些使用isKindOfClass判断的时候出错。
  2. isKVO方法作为能否实现KVO功能的一个标识。
  3. delloc里面还原isa指针

KVO & KVC 问题总结

3.1 isa混写之后如何调用方法?

  1. 调用监听的属性设置方法,例如setAge:,都会先调用NSKVONotify_Person对应的属性设置方法。
  2. 调用非监听属性设置方法,如test,会通过NSKVONotify_Personsuperclass来找到Person类对象,再调用起[Person test]方法。

3.2 为什么在生成的子类内部重写class方法

如果没有重写class方法,当对象调用class方法的时候,会在自己的方法缓存列表、方法列表、父类缓存、方法列表一直向上去查找该方法,因为class方法是NSObject中的方法,如果不重写最终可能会返回NSKVONotifying_Person,就会将该类暴露出来。

3.3 直接修改成员变量的值,会不会触发KVO?

不会触发KVO,KVO的本质是替换了setter方法的实现,所以只有通过set方法修改才会触发KVO。

3.4 KVC修改属性会触发KVO吗?

会的 ,尽管setvalue:forkey:方法不一定会触发instance实例对象的setter:方法,但是setvalue:forkey:在更改成员变量值的时候,会手动调用willchangevalueforkeydidchangevalueforkey,会触发监听器的回调方法。

3.5 KVO怎么监听数组的元素变化?

  • KVO本来只能监听数组长度的变化,不能监听内部对象的变化,我们可以手动KVC修改数组内部的元素达到目的。请添加图片描述

我们可以通过KVC来对数组进行添加元素的操作,这样就可以监听到了。通过KVC的mutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。集合对象包含NSArray和NSSet。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/51264.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Glow: Generative Flow with Invertible 1×1 Convolutions论文解析及实现(二)

Glow: Generative Flow with Invertible 11 Convolutions 代码github: https://github.com/rosinality/glow-pytorch添加链接描述 1 模型架构如下 1.1 左边图flow模型 Flow model ① ActNorm ② InvConv2dLU ③ AffineCoupling 1.2 右边模型结构Glow模型 Glow Model Block…

【Linux】-进程概念及进程状态(僵尸进程和孤儿进程)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

更安全,更省心丨DolphinDB 数据库权限管理系统使用指南

在数据库产品使用过程中&#xff0c;为保证数据不被窃取、不遭破坏&#xff0c;我们需要通过用户权限来限制用户对数据库、数据表、视图等功能的操作范围&#xff0c;以保证数据库安全性。为此&#xff0c;DolphinDB 提供了具备以下主要功能的权限管理系统&#xff1a; 提供用户…

OpenMP

官方文档&#xff1a;OpenMP | LLNL HPC Tutorials OpenMP总览 统一内存访问&#xff1a;OpenMP、Pthreads 非统一内存访问&#xff1a;MPI OpenMP与Pthread OpenMP原理 串行区到达并行区后会派生多个线程&#xff0c;并行区代码执行完后进行线程合并&#xff0c;剩下主线程 编…

Linux - PostgreSQL 适用于9.x 以上的 tar.gz 源码安装与理解 - 报错集锦

这里写目录标题 序言主要内容bash 配置文件个人理解关于初始化 PostgreSQL 数据库的理解 启动方法检查服务器是否在PostgreSQL中运行关闭 postgresql 数据库方法参考链接 序言 PostgreSQL 9.x 以下版本笔者没用过&#xff0c;具体操作看参考链接&#xff0c;笔者就不记录重复操…

MODBUS-TCP转Ethernet IP 网关连接空压机 配置案例

本案例是工业现场应用捷米特JM-EIP-TCP的Ethernet/IP转Modbus-TCP网关连接欧姆龙PLC与空压机的配置案例。使用设备&#xff1a;欧姆龙PLC&#xff0c;捷米特JM-EIP-TCP网关&#xff0c; ETHERNET/IP 的电气连接 ETHERNET/IP 采用标准的 T568B 接法&#xff0c;支持直连和交叉接…

在centos 7系统docker上构建mysql 5.7

一、VM上已经安装centos 7.9&#xff0c;且已完成docker的构建 二、安装mysql5.7 安装镜像&#xff1a;[rootlocalhost lll]# docker pull mysql:5.7 查看镜像[rootlocalhost lll]# docker images 根据镜像id构建mysql容器&#xff0c;且分配端口号[rootlocalhost lll]# dock…

自定义view - 玩转字体变色

自定义View步骤&#xff1a; 1>&#xff1a;values__attrs.xml&#xff0c;定义自定义属性&#xff1b; 2>&#xff1a;在第三个构造方法中获取自定义属性&#xff1b; 3>&#xff1a;onMeasure【不是必须的】&#xff1b; 4>&#xff1a;onDraw&#xff1a;绘制代…

emacs打开git仓库下多个子工程的根目录问题解决案

emacs打开git仓库下多个子工程的根目录问题解决案 问题描述 如题所述&#xff0c;这个问题困扰我很久了&#xff0c;一直没搜到完整的解决方案。这次终于乘着空闲时间&#xff0c;研究了projectile.el源码找到了方案。 问题场景具体描述下: 我自己有一个私人git仓库&#x…

机器学习:GPT3

GPT3 模型过于巨大 GPT3是T5参数量的10倍&#xff01; 训练GPT3的代价是$12百万美元 Zero-shot Ability GPT3的思想是不是能拿掉Fine-tune 只需要给定few-shot或者zero-shot就能干相应的任务了。 few-shot learning&#xff08;no gradient descent&#xff09;&#…

(学习笔记)matplotlib.pyplot模块下基本画图函数的整理

matplotlib版本&#xff1a;3.7.1 python版本&#xff1a;3.10.12 基本函数 matplotlib版本&#xff1a;3.7.1python版本&#xff1a;3.10.12 1. plt.plot()函数1.1 plt.plot(x, y)1.2 plt.plot(x, y, **kwargs) 2. plt.xlable(), plt.ylable()3. plt.title()4. plt.show()5.p…

SkyWalking链路追踪-技术文档首页

SkyWalking 文档中文版&#xff08;社区提供&#xff09; (skyapm.github.io)https://skyapm.github.io/document-cn-translation-of-skywalking/ SkyWalking-基本概念 SkyWalking链路追踪是一个用于分布式系统的性能监控工具&#xff0c;它帮助开发人员了解系统中各组件之间…

向量vector模板输出、倒置、求和

运行代码&#xff1a; //向量vector模板输出、倒置、求和 #include"std_lib_facilities.h" //定义vector<double>的输入操作符>> istream& operator>>(istream& is, vector<double>& vv) {double dd0.0;if(is >> dd)vv.p…

【如何训练一个中英翻译模型】LSTM机器翻译模型部署之ncnn(python)(五)

系列文章 【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中英翻译模型】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训练…

Unity光照相关知识和实践 (烘焙光照,环境光设置,全局光照)

简介 本文将会通过一个简单的场景搭建&#xff0c;介绍如何使用烘焙光照以及相关的注意事项。另外还介绍了Unity内全局光照&#xff08;GI&#xff09;的知识和GI实际在游戏内的表现效果。 Unity关于光照相关的参考文档地址&#xff1a;https://docs.unity.cn/cn/current/Man…

Linux CentOS快速安装VNC并开启服务

以下是在 CentOS 上安装并开启 VNC 服务的步骤&#xff1a; 安装 VNC 服务器软件包。运行以下命令&#xff1a; sudo yum install tigervnc-server 输出 $ sudo yum install tigervnc-server Loaded plugins: fastestmirror, langpacks Repository epel is missing name i…

计算机论文中名词翻译和解释笔记

看论文中一些英文的简写不知道中文啥意思&#xff0c;或者一个名词不知道啥意思。 于是自己做了一个个人总结。 持续更新 目录 SoftmaxDeep Learning(深度学习)循环神经网络(Recurrent Neural Network简称 RNN)损失函数/代价函数(Loss Function)基于手绘草图的三维模型检索(Ske…

【笔记】PyTorch DDP 与 Ring-AllReduce

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 文内若有错误&#xff0c;欢迎指出&#xff01; 今天我想跟大家分享的是一篇虽然有点老&#xff0c;但是很经典的文章&#xff0c;这是一个在分布式训练中会用到的一项技术&#xff0c; 实际上叫ringallreduce。 …

用html+javascript打造公文一键排版系统8:主送机关排版

公文一般在标题和正文之间还有主送机关&#xff0c;相关规定为&#xff1a; 主送机关 编排于标题下空一行位置&#xff0c;居左顶格&#xff0c;回行时仍顶格&#xff0c;最后一个机关名称后标全角冒号。如主送机关名称过多导致公文首页不能显示正文时&#xff0c;应当将主送机…

redis的并发安全问题:redis的事务VSLua脚本

redis为什么会发生并发安全问题&#xff1f; 在redis中&#xff0c;处理的数据都在内存中&#xff0c;数据操作效率极高&#xff0c;单线程的情况下&#xff0c;qps轻松破10w。反而在使用多线程时&#xff0c;为了保证线程安全&#xff0c;采用了一些同步机制&#xff0c;以及多…