【iOS】—— KVO与KVC

KVO与KVC

    • 1. KVO
      • KVO底层实现分析
        • 如何验证上面的说法:
        • NSKVONotifyin_Person内部结构
        • didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法
      • 回答问题:
    • 2. KVC
      • 简介:
      • key和keyPath的区别
        • key:
        • keyPath:
      • 批量重复操作
      • 字典模型相互转化
      • KVC原理
        • 赋值原理
        • 取值原理

1. KVO

首先需要了解KVO基本使用,KVO的全称 Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变。

 #import "ViewController.h"
#import "Person.h"
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.Person *p1 = [[Person alloc] init];Person *p2 = [[Person alloc] init];p1.age = 1;p1.age = 2;p2.age = 2;[p1 addObserver:self  forKeyPath:@"age"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];p1.age = 10;[p1 removeObserver:self forKeyPath:@"age"];}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {NSLog(@"监听到%@的%@改变了%@", object, keyPath,change);NSLog(@"%@", change[@"old"]);NSLog(@"%@", change[@"new"]);}
@end

在这里插入图片描述

KVO底层实现分析

KVO 的底层实现利用了 Objective-C 的 Runtime 特性,动态创建子类并重写属性的 setter 方法,以实现属性变化的观察和通知机制。 在这里插入图片描述
通过上面我们可以发现,再对象p1对象执行addObserver操作之后,p1对象的isa指针由之前的指向类对象Person变为指向NSKVONotifyin_Person类对象,而p2对象没有任何改变。也就是说一旦p1对象添加了KVO监听以后,其isa指针就会发生变化,因此set方法的执行效果就不一样了。

未使用KVO监听的对象放大实现路径

上图是p1没有执行addObserver操作之前isa指针的实际指向。但是,在p1添加addObserver操作之后,p1对象的isa指针就如上面所示指向为NSKVONotifying_Person,NSKVONotifying_Person是Person的子类,也就是说其superclass指针是指向Person类对象的,NSKVONotifyin_Person是runtime在运行时生成的。那么p1对象在调用setage方法的时候,肯定会根据p1的isa找到NSKVONotifyin_Person,在NSKVONotifyin_Person中找setage的方法及实现。

如何验证上面的说法:
 NSLog(@"添加KVO监听之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);[p1 addObserver:self  forKeyPath:@"age"options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];NSLog(@"添加KVO监听之前 - p1 = %p, p2 = %p", [p1 methodForSelector: @selector(setAge:)],[p2 methodForSelector: @selector(setAge:)]);

在这里插入图片描述
我们可以发现:**添加KVO监听之前,p1和p2的setAge方法实现的地址相同,而经过KVO监听之后,p1的setAge方法实现的地址发生了变化,**我们通过打印方法实现来看一下前后的变化发现,确实如我们上面所讲的一样,p1的setAge方法的实现由Person类方法中的setAge方法转换为了C语言的Foundation框架的_NSsetIntValueAndNotify函数。

NSKVONotifyin_Person内部结构

首先我们清楚NSKVONotifyin_Person,作为Person的子类,其superclass指针指向Person类,并且NSKVONotifyin_Person内部一定对setAge方法做了单独的实现,

 - (void)viewDidLoad {[super viewDidLoad];Person *p1 = [[Person alloc] init];p1.age = 1.0;Person *p2 = [[Person alloc] init];p1.age = 2.0;// self 监听 p1的 age属性NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;[p1 addObserver:self forKeyPath:@"age" options:options context:nil];[self printMethods: object_getClass(p2)];[self printMethods: object_getClass(p1)];[p1 removeObserver:self forKeyPath:@"age"];
}- (void) printMethods:(Class)cls
{unsigned int count ;Method *methods = class_copyMethodList(cls, &count);NSMutableString *methodNames = [NSMutableString string];[methodNames appendFormat:@"%@ - ", cls];for (int i = 0 ; i < count; i++) {Method method = methods[i];NSString *methodName  = NSStringFromSelector(method_getName(method));[methodNames appendString: methodName];[methodNames appendString:@" "];}NSLog(@"%@",methodNames);free(methods);
}

运行结果如下:
在这里插入图片描述
通过上述代码我们发现NSKVONotifyin_Person中有4个对象方法。分别为setAge: class dealloc _isKVOA,那么至此我们可以画出NSKVONotifyin_Person的内存结构以及方法调用顺序。

NSKVONotifyin_Person的内存结构以及方法调用顺序

**这里NSKVONotifyin_Person重写class方法是为了隐藏NSKVONotifyin_Person。**不被外界所看到。我们在p1添加过KVO监听之后,分别打印p1和p2对象的class可以发现他们都返回Person。

如果NSKVONotifyin_Person不重写class方法,那么当对象要调用class对象方法的时候就会一直向上找来到NSObject,而NSObject的class的实现大致为返回自己isa指向的类,返回p1的isa指向的类那么打印出来的类就是NSKVONotifyin_Person,apple不希望将NSKVONotifyin_Person类暴露出来,并且不希望我们知道NSKVONotifyin_Person内部实现,所以在内部重写了class类,直接返回Person类。

didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法

在Person类中重写willChangeValueForKey:和didChangeValueForKey:方法,模拟他们的实现。

- (void)setAge:(int)age {NSLog(@"setage:");_age = age;}- (void)willChangeValueForKey:(NSString *)key {NSLog(@"willChangeValueForKey: - begin");[super willChangeValueForKey:key];NSLog(@"willChangeValueForKey: - end");
}- (void)didChangeValueForKey:(NSString *)key
{NSLog(@"didChangeValueForKey: - begin");[super didChangeValueForKey:key];NSLog(@"didChangeValueForKey: - end");
}

运行结果:
在这里插入图片描述

回答问题:

  1. iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) 答. 当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。
  1. 如何手动触发KVO? 答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey和didChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。

2. KVC

简介:

KVC的全称是KeyValueCoding,俗称“键值编码”,可以通过一个key来访问某个属性;

KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量;

它是一个非正式的Protocol,提供一种机制来间接访问对象的属性,而不是通过调用Setter、Getter方法访问。KVO 就是基于 KVC 实现的关键技术之一。

常见的API:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;

key和keyPath的区别

  • key:只能接受当前类所具有的属性,不管是自己的,还是从父类继承过来的。
  • keypath:除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链。
key:
Person* person = [[Person alloc] init];
[person setValue:@"I am Father" forKey:@"name"];
NSLog(@"%@", [person valueForKey:@"name"]);

输出结果:
在这里插入图片描述

keyPath:
person.son = [[PersonSon alloc] init];
[person setValue:@"I am Son" forKeyPath:@"son.sonName"];
NSLog(@"%@", [person.son valueForKey:@"sonName"]);

输出结果:
在这里插入图片描述

批量重复操作

 Person* personFirst = [[Person alloc] init];[personFirst setValue:@"lcy" forKey:@"name"];[personFirst setValue:@"20" forKey:@"age"];[personFirst setValue:@"男" forKey:@"sex"];NSLog(@"name = %@, age = %ld, sex = %@",personFirst.name, (long)personFirst.age, personFirst.sex);NSDictionary* dictionary1 = [personFirst dictionaryWithValuesForKeys:@[@"name", @"age", @"sex"]];NSLog(@"dictionary1 = %@", dictionary1);NSDictionary* dictioinary2 = @{@"name": @"lyt", @"age": @11, @"sex": @"男"};Person* personSecond = [[Person alloc] init];[personSecond setValuesForKeysWithDictionary:dictioinary2];NSLog(@"name = %@, age = %ld, sex = %@",personSecond.name, (long)personSecond.age, personSecond.sex);

输出结果:
在这里插入图片描述

字典模型相互转化

如果model属性和dic不匹配,可以重写方法-(void)setValue:(id)value forUndefinedKey:(NSString *)key

*重点:-(void)setValue:(id)value forUndefinedKey:(NSString )key 方法在函数中有定义,但是没有实现需要自己来实现,从而供后面来调用。如果自己不重写的话,遇到Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface StudentModel : NSObject@property (nonatomic, strong) NSString* name;
@property (nonatomic, strong) NSString* age;
@property (nonatomic, strong) NSString* studentSex;@endNS_ASSUME_NONNULL_END#import "StudentModel.h"@implementation StudentModel
- (void) setValue:(id)value forUndefinedKey:(NSString *)key {if ([key isEqualToString:@"sex"]) {self.studentSex = (NSString*) value;}
}
@end//main函数
NSDictionary* dictionary = @{@"name": @"stu1", @"age": @66, @"sex": @"nv"};
StudentModel* model = [[StudentModel alloc] init];
[model setValuesForKeysWithDictionary:dictionary];
NSLog(@"model.name: %@", model.name);
NSLog(@"model.age: %@", model.age);
NSLog(@"model.sex: %@", model.studentSex);NSDictionary* tempdict = [model dictionaryWithValuesForKeys:@[@"name", @"age", @"studentSex"]];
NSLog(@"tempdict = %@", tempdict);

输出结果:

在这里插入图片描述

KVC原理

赋值原理

在日常开发中,针对对象属性的赋值,一般有以下两种方式:

  • 直接通过setter方法赋值;
  • 通过KVC键值编码的相关API赋值;

当调用setValue:forKey:设置属性value时,其底层的执行流程为:

  1. 【第一步】首先查找是否有这三种setter方法,按照查找顺序为set<Key>:-> _set<Key> -> setIs<Key>
    • 如果有其中任意一个setter方法,则直接设置属性的value(主注意:key是指成员变量名,首字符大小写需要符合KVC的命名规范)
    • 如果都没有,则进入【第二步】
  2. 【第二步】:如果没有第一步中的三个简单的setter方法,则查找accessInstanceVariablesDirectly是否返回YES,
    • 如果返回YES,则查找间接访问的实例变量进行赋值,查找顺序为:_<key> -> _is<Key> -> <key> -> is<Key>
      • 如果找到其中任意一个实例变量,则赋值。
      • 如果都没有,则进入【第三步】。
    • 如果返回NO,则进入【第三步】。
  3. 【第三步】如果setter方法 或者 实例变量都没有找到,系统会执行该对象的setValue:forUndefinedKey:方法,默认抛出NSUndefinedKeyException类型的异常。
    综上所述,KVC通过 setValue:forKey: 方法设值的流程以设置LGPerson的对象person的属性name为例,如下图所示:

setValue:forKey:的原理

取值原理
  1. kvc取值按照 getKey、key、iskey、_key 顺序查找方法。
  2. 存在直接调用。
  3. 没找到同样,先查看accessInstanceVariablesDirectly方法。
+ (BOOL)accessInstanceVariablesDirectly{return YES;   ///> 可以直接访问成员变量//    return NO;  ///>  不可以直接访问成员变量,  ///> 直接访问会报NSUnkonwKeyException错误  }
  1. 如果可以访问会按照 _key、_isKey、key、iskey的顺序查找成员变量。
  2. 找到直接复制。
  3. 未找到报错NSUnkonwKeyException错误。

valueForKey:的原理

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“支付”到“智付” 数字人民币场景加速落地

在全球化浪潮的推动下&#xff0c;跨境电商如雨后春笋般涌现&#xff0c;它们跨越国界&#xff0c;将商品和服务直接送达消费者手中。然而&#xff0c;当外国的跨境电商希望进入中国市场时&#xff0c;他们面临着一个共同的挑战&#xff1a;如何合法合规地从中国用户手中收取款…

探索 Electron:如何进行网址收藏并无缝收录网页图片内容?

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

EtherNet/IP转CAN协议转化网关(功能与配置)

怎么样把EtherNet/IP和CAN两个协议连接起来?有很多朋友想要了解这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-EIP-ECAT网关。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主…

Angular 遍历列表时的key

在Angular中&#xff0c;你可以使用keyvalue管道来遍历对象的键。这里是一个简单的例子&#xff0c;展示了如何在Angular模板中使用它&#xff1a; <div *ngFor"let key of myObject | keyvalue:key">Key: {{ key }} - Value: {{ myObject[key] }} </div&g…

springboot中hutool-core依赖的使用

springboot中hutool-core依赖的使用 依赖安装1、StrUtil.isBlank() 依赖安装 <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-core --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId><ver…

在Android开发中,如何优化onCreate()和onResume()方法以提高应用性能?

在Android开发中&#xff0c;onCreate()和onResume()方法是活动生命周期中非常重要的两个回调方法&#xff0c;它们分别在活动创建和重新获得焦点时被调用。为了提高应用的性能&#xff0c;以下是一些优化这两个方法的策略&#xff1a; 对于onCreate()方法的优化&#xff1a; …

聊聊基于Alink库的主成分分析(PCA)

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维和特征提取技术&#xff0c;用于将高维数据转换为低维的特征空间。其目标是通过线性变换将原始特征转化为一组新的互相无关的变量&#xff0c;这些新变量称为主成分&…

TinyMCE一些问题

1.element 在el-dialog中使用tinymce导致富文本弹窗在el-dialog后面的问题 原因是富文本的弹窗层级太低了 在APP.vue中添加样式即可解决 /* 富文本菜单 */ .tox-tinymce-aux {z-index: 9999 !important; }2.element 在el-dialog中点击富文本的功能栏报错 由于 aria-hidden 属…

Midjourney、Sora和硅谷机密-《分析模式》漫谈15

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的Preface&#xff08;前言&#xff09;有这么一句&#xff1a; Kent Beck, Ward Cunningham, and Jim Coplein encouraged me to get involved with the commu…

虚假的互联网信息?不妨从IT的角度理解【景观社会】

博主前言&#xff1a;“我思故我在”&#xff0c;笛卡尔的这一哲学命题&#xff0c;大抵上次还比较熟络的时光还是高中亦或复习考研政治的岁月里。这是一个光怪陆离的社会——或者说网络社会&#xff0c;形形色色的消息充斥在脑海之时&#xff0c;你是否还能认识真正的自己&…

YOLOV8-源码解读-SPP-SPPF

先给出YOLOV8中一键三连卷积模块 def autopad(k, pNone, d1): # kernel, padding, dilation"""Pad to same shape outputs."""if d > 1:k d * (k - 1) 1 if isinstance(k, int) else [d * (x - 1) 1 for x in k] # actual kernel-sizeif…

全国区块链职业技能大赛样题第9套智能合约+数据库表设计

后端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746050 前端源码地址:https://blog.csdn.net/Qhx20040819/article/details/140746216 智能合约+数据库表设计:https://blog.csdn.net/Qhx20040819/article/details/140746646 nice.sql /* Navicat MySQ…

分布式事务解决方案(一) 2PC、3PC、TCC、Sega

目录 1.绪论 2.2PC 2.1 基本原理 2.1.1 组成 2.1.2 步骤 1.prepare阶段 2.commit阶段 2.2 2PC 存在的问题 2.2.1 阻塞问题 2.2.2 单点故障问题 1. 事务协调器宕机 2.部分数据不一致问题 2.资源管理器宕机 3. 事务协调器和资源管理管理器同时宕机 2.2 实现 2.2.1…

怎么将几个pdf合成为一个pdf?pdf合成为一个的常用方法

在现代的职场和学术环境中&#xff0c;如何将多个独立的PDF文档合并成一个统一的文件已经成为提高工作效率、优化文档管理和促进信息共享的重要手段。PDF格式以其卓越的跨平台兼容性、强大的数据保护能力以及清晰易读的版面设计&#xff0c;在全球范围内得到了广泛的应用和认可…

2-45 基于matlab的递归最小二乘法(RLS)对声音信号去噪

基于matlab的递归最小二乘法&#xff08;RLS&#xff09;对声音信号去噪,并对消噪前后的信号进行FFT分析&#xff0c;对比消噪前后的效果。可替换自己的声音信号进行分析。程序已调通&#xff0c;可直接运行。 2-45 递归最小二乘法&#xff08;RLS&#xff09; FFT分析 - 小红书…

系统移植(七)u-boot移植 ④ trusted版本

文章目录 一、U-boot源码适配&#xff08;一&#xff09;执行make stm32mp15_trusted_defconfig命令进行配置&#xff0c;生成.config文件&#xff08;二&#xff09;执行make menuconfig命令&#xff0c;对u-boot源码进行重新配置1. 对u-boot源码进行配置&#xff0c;移除pmic…

wire和reg的区别

在 Verilog 中&#xff0c;wire 和 reg 是两种不同的数据类型&#xff0c;用于表示信号或变量。它们在 Verilog 中的使用场景和行为有一些区别&#xff1a; ### wire&#xff1a; - wire 类型用于连接组合逻辑电路中的信号&#xff0c;表示电路中的连线或信号传输线。 - wire …

【C++进阶学习】第十弹——哈希的原理与实现——链地址法的原理与讲解

开放地址法&#xff1a;【C进阶学习】第九弹——哈希的原理与实现——开放寻址法的讲解-CSDN博客 前言&#xff1a; 哈希的整体思想就是建立映射关系&#xff0c;前面的开放地址法的讲解中&#xff0c;也对哈希的原理做了详细的讲解&#xff0c;今天就来讲解一下实现哈希的另一…

Java NIO (一)

因工作需要我接触到了netty框架&#xff0c;这让我想起之前为夺高薪而在CSDN购买的Netty课程。如今看来&#xff0c;这套课程买的很值。这套课程中关于NIO的讲解&#xff0c;让我对Tomcat产生了浓厚的兴趣&#xff0c;于是我阅读了Tomcat中关于服务端和客户端之间连接部分的源码…