【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,一经查实,立即删除!

相关文章

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

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

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

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

聊聊基于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…

分布式事务解决方案(一) 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…

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

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

Java NIO (一)

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

如何快速获取全网精准客流?揭秘不为人知的5大运营策略!

有同行所在的地方&#xff0c;就一定拥有咱们需要的客户。客户看的是结果&#xff0c;搜索的是问题&#xff0c;寻找的是答案。 如果没有付费流量&#xff0c;单纯靠搞免费流量&#xff0c;很多大厂的运营也会变得一文不值。一个牛逼的运营&#xff0c;不仅是会做付费流量&…

leetcode10 -- 正则表达式匹配

题目描述&#xff1a; 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 示例 1&#xff1…

PDF转Word后不能修改怎么办?是什么原因呢?

平时在生活中&#xff0c;很多朋友都会有将PDF转换成Word文档的需求&#xff0c;因为一般情况下PDF文件是不能直接编辑修改的&#xff0c;所以只能通过这种方式来解决问题。但是近期&#xff0c;有部分用户在后台反馈说PDF转Word后不能修改怎么办呢&#xff1f;其实这个问题也是…

第1-3章Excel数据分析基础

文章目录 第1章&#xff1a;使用统计函数做数据分析1-1常用统计函数应用1-2条件统计函数1-3多条件统计函数1-4条件统计函数中的通配符1-5将条件统计函数中的条件数组化1-6单条件文本合并-新增函数1-7多条件与模仿通配符的文本合并 第2章&#xff1a;数据分析之合并计算2-1合并计…

备忘录系统

目录 一、 系统简介 1.简介 2需求分析 3 编程环境与工具 二、 系统总体设计 1 系统的功能模块图。 2 各功能模块简介 3项目结构 4 三、 主要业务流程 &#xff08;1&#xff09;用户及管理员登录流程图 &#xff08;2&#xff09;信息添加流程 &#xff0…

攻防世界 re 新手模式 2

IgniteMe 32位无壳 一些简单信息&#xff0c;看关键函数 可以得到v7[i]的值 比较简单的逆向 #include<stdio.h> #include<string.h> int main() {char flag[40];char s[40];char str[]"GONDPHyGjPEKruv{{pj]XrF";char ch[] {0x0D, 0x13, 0x17, 0x11,…