iOS--KVO和KVC

news2024/12/26 14:36:19

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:只能接受当前类所具有的属性,不管是自己的,还是从父类继承过来的,如view.setValue(CGRectZero(),key: "frame");
keypath:除了能接受当前类的属性,还能接受当前类属性的属性,即可以接受关系链,如view.setValue(5,keypath: "layer.cornerRadius");

KVC原理

setValue:forKey: 的原理(KVC赋值原理)

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

valueForKey:的原理(KVC取值原理)

  • 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
  • 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常;
  • 若返回的YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
  • 找不到则调用valueForUndefinedKey:抛出NSUnknowKeyExpection异常;
    在这里插入图片描述

注意

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

KVC强大功能

批量存值操作

KVC还有更强大的功能,可以根据给定的一组key,获取到一组value,并且以字典的形式返回

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

批量赋值操作

同样,也可以通过KVC进行批量操作,使用对象调用 setValuesForKeysWithDictionary:方法时,可以传入一个包好key、value的字典进去,KVC可以将所有数据按照属性名和字典的key进行匹配,并将value给对象的属性赋值。

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
Person *personFirst = [[Person alloc] init];
    [personFirst setValue:@"buer" forKey:@"name"];
    [personFirst setValue:@11 forKey:@"age"];
    [personFirst setValue:@"男" forKey:@"sex"];
    NSDictionary *dictionaryFirst = [personFirst dictionaryWithValuesForKeys:@[@"name", @"age", @"sex"]];
    NSLog(@"dictionaryFirst = %@", dictionaryFirst);
    NSDictionary *dictionarySecond = @{@"name":@12, @"age":@11, @"sex":@"女"};
    Person *personSecond = [[Person alloc] init];
    [personSecond setValuesForKeysWithDictionary:dictionarySecond];
    NSLog(@"name = %@, age = %ld, sex = %@",personSecond.name, (long)personSecond.age, personSecond.sex);

字典转模型

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

//StudentModel.h
@interface StudentModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
@property (nonatomic, strong) NSString *studentSex;

@end

@implementation StudentModel

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

@end

在这里插入图片描述

模型转字典

在这里插入图片描述

KVO

简单介绍

KVO的全称是KeyValueObserving,俗称“键值监听",可以用于监听某个对象属性值的改变;

KVO是苹果提供的在套事件通知机制。KVONSNotificationCenter都是iOS中观察者模式的一种实现,区别是:NSNotificationCenter可以是一对多的关系,而KVO是一对一的;

KVO的使用

KVO使用步骤

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

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

keyPath:被观察对象的属性
object:被观察的对象
change:字典,存放相关的值,根据options传入的枚举来返回新值旧值
context:注册观察者的时候,context传递过来的值
移除KVO监听
在不需要监听的时候,通过方法[removeObserver:forKeyPath:],移除监听;

KVO传值

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

其它

如果想控制当前对象的自动调用过程,也就是由上面两个方法发起的KVO调用,则可以重写下面方法。方法返回YES则表示可以调用相关对象的监听事件,如果返回NO则表示不可以调用相关对象的监听事件。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    } else {
        return [super automaticallyNotifiesObserversForKey:key];
    }
}

这样我们该类name属性就算变化了也不会调用监听事件了!注意:这个函数必须要定义在你不想调用的类中,这样才表示你不想监听该类的name属性。

注意

调用[removeObserver:forKeyPath:]需要在观察者消失之前,否则会导致Crash
在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,否则会导致观察者被释放带来的Crash
观察者需要实现observeValueForKeyPath:ofObject:change:context:方法,当KVO事件到来时会调用这个方法,如果没有实现会导致Crash。
KVO的addObserverremoveObserver需要是成对的,如果重复remove则会导致NSRangeException类型的Crash,如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash
在调用KVO时需要传入一个keyPath,由于keyPath是字符串的形式,所以其对应的属性发生改变后,字符串没有改变容易导致Crash。我们可以利用系统的反射机制将keyPath反射出来,这样编译器可以在@selector()中进行合法性检查。

KVO原理

KVO的本质是改变setter方法的调用:

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

//person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, strong) NSString *name;
@property (assign) NSInteger age;
- (void)printInfo;
@end

//person.m
#import "Person.h"
#import <objc/runtime.h>

@implementation Person

- (void)printInfo {
    NSLog(@"isa:%@, super class:%@", NSStringFromClass(object_getClass(self)),
          class_getSuperclass(object_getClass(self)));
    
    NSLog(@"self:%@, [self superclass]:%@", self, [self superclass]);
    
    NSLog(@"age setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setAge:)));
    
    NSLog(@"name setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setName:)));
    NSLog(@"printInfo function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(printInfo)));
}

@end

添加监听和移除监听:

self.myPerson = [[Person alloc] init];
    self.myPerson.name = @"p1";
    //打印监听前类信息
    [self.myPerson printInfo];
    // KVO是监听对象的属性值的改变的
    [self.myPerson addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    self.myPerson.name = @"123";
    //打印监听后类信息
     [self.myPerson printInfo];
     [self.myPerson removeObserver:self forKeyPath:@"name"];
     //打印移除监听后类信息
     [self.myPerson printInfo];

通过输出结果发现,其监听之后确实创建了一个中间类NSKVONotifying_Person,并且其监听属性的setter方法的地址还发生了改变,通过上边所说应该是重写了原来类的setter方法,从而达到了监听的目的。在我们移除监听之后,该类的isa又恢复指向为原来的类了,说明在监听的时候它就会创建一个中间类,监听结束之后就会撤销中间类,并且在我们监听的时候,该类的其他属性的setter地址也没有变化,我们大约能够推断出,他应该会通过superClass指针来获取之前的setter地址,为其赋值,因为superClass指向的还是原来的类。

_NSSetXXXValueAndNotify的内部实现原理

先调用willChangeValueForKey方法,
再调用父类原来的setter方法
最后调用didChangeValueForKey,其内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:);

我们在之前的willChangeValueForKey方法和didChangeValueForKey方法中间添加了输出信息,通过在控制台的输出,我们就能看到监听类的setter方法调用确实如上所说,并且监听事件还是在didChangeValueForKey方法中进行调用的,所以我们大胆推断didChangeValueForKey方法实现大概是这样的:

综上总结,监听属性的setter方法会被中间类重写,并且重写后的结构大概是这样:在这里插入图片描述

手动调用KVO

我们大概知道了KVO的原理之后,那么我们想自己实现手动调用KVO岂不是手到擒来。步骤如下:

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

注意

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

KVO的所作所为

  • 在设置监听的时候,runtime动态的创建该类的子类,并重写了其setter方法。
  • class方法屏蔽内部实现隐藏了类的存在,避免开发者造成困扰。如果不重写class方法的话,会沿着继承链找到NSObject类中,而class的实现是返回了当前对象所属的类。
  • 重写了dealloc,进行一些释放收尾工作。
  • 添加_isKVO变量,判断是否被KVO动态生成子类。

KVO关键

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

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

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

为什么重写class方法?

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

遇到的问题

直接修改成员变量的值,会不会触发KVO?
不会,KVO的本质是生成一个子类,重写父类的setter方法,在新的setter方法里面调用Foundation_NSSetXXXValueAndNotify函数,直接修改成员变量的值,不会执行setter,所以不会触发KVO。

KVC修改属性会触发KVO吗?
会的 ,尽管setvalue:forkey:方法不一定会触发instance实例对象的setter:方法,但是setvalue:forkey:在更改成员变量值的时候,会手动调用willchangevalueforkey、didchangevalueforkey,会触发监听器的回调方法。
KVO与代理的效率问题?
KVO的效率比代理的效率低,因为KVO需要动态地生成这个类的子类NSKVONotifying_className,耗时。
为什么KVC总提到对象的成员变量,而不是属性呢?
如果是属性的话,系统自动帮我们实现了set方法,所以KVC总是可以找到它需要的setKey:方法。如果是成员变量,系统就不会为你实现set方法了
KVO怎么监听数组的元素变化?
我们可以通过KVC来对数组进行添加元素的操作,这样就可以监听到了。通过KVC的mutableArrayValueForKey:等方法获得代理对象,当代理对象的内部对象发生改变时,会回调KVO监听的方法。集合对象包含NSArrayNSSet。
它的原理是,你调用的mutableArrayValueForKey会调用willchangevalueforkey和didchangevalueforkey,触发了KVO的监听。

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

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

相关文章

数据结构笔记

数据结构笔记 1. 绪论 随着计算机深入到各个领域&#xff0c;它的作用已不再局限于科学计算&#xff0c;而更多的用于控制&#xff0c;管理及数据处理等非数值计算的处理工作。而它加工出理的对象也由纯粹的数值发展到字符&#xff0c;表格和图像等各种具有一定结构的数据。…

overleaf上踩的坑~

IEEE模板伪代码 问题&#xff1a; 格式显示不对&#xff0c;且 \\ 换行后不会自动标序号 解决办法&#xff1a; 在宏包中只保留 \usepackage[algo2e,linesnumbered,lined,boxed,commentsnumbered,ruled]{algorithm2e} 删除其他伪代码宏包&#xff1a; %\usepackage{algorith…

“嵌入式应用:超越想象的智能化时代“

从传统的家电如空调、电饭煲、电风扇到现代的智能手机、路由器、洗衣机&#xff0c;嵌入式技术无处不在。这些设备通过结合互联网和智能化功能&#xff0c;变得更加智能和便捷。例如&#xff0c;通过添加Wi-Fi模块&#xff0c;空调可以实现远程控制和预约开机功能。 嵌入式技术…

做独立站跨境出海,需要考虑哪些要素?

对于不少中小卖家而言&#xff0c;利用独立站出海已然成为下一个跨境热潮。但是采用独立站模式做出海生意前&#xff0c;卖家需要考虑哪些要素&#xff1f; 产品选择 对于国内的卖家来说&#xff0c;依托于国内强大的供应链优势&#xff0c;只要能把握住消费者心态&#xff0c;…

Linux 为什么还要坚持使用宏内核?

假如一家三口都生活在同一个房间&#xff0c;那么可以直接说话&#xff0c;对方都能听到&#xff0c;这样子效率就比较高。 假如一家三口都有自己的房间&#xff0c;那么对话时&#xff0c;一方就需要跑到对方的房间去了。这样子效率就会下降。 对于内核来说&#xff0c;第一…

PDF怎么转成Excel?4个方法非常实用!

如何使用记灵在线工具将PDF转成Excel&#xff1f;在日常工作中&#xff0c;我们经常需要转换PDF文件为Excel文件以方便我们处理数据。虽然PDF格式对于文本和图片的可视化效果效果不错&#xff0c;但是在处理数据时&#xff0c;Excel表格更加便捷。当我们将PDF文件转换成Excel文…

基于 STM32+FPGA 的通用工业控制器设计(一)系统方案设计

本章首先介绍了现有 PLC 系统的概况&#xff0c;然后提出了本文设计的通用工业控制器的 整体方案架构&#xff0c;分析了硬件和软件上需要实现的功能&#xff0c;最后对各部分功能进行分析并提 出具体的实现方案。 2.1 PLC 系统简介 可编程逻辑控制器&#xff08; Progra…

linux----软连接和硬链接

介绍 # 文件分 文件名 inode信息:文件类型&#xff0c;大小&#xff0c;位置,时间 文件真正存储位置block# 软连接---》复制一份inode信息(win的快捷方式)软链接文件会将inode指向源文件的block&#xff0c;当我们访问这个软链接文件时&#xff0c;其实访问的是源文件本身…

数据决定AIGC的高度,什么又决定着数据的深度?

有人曾言&#xff0c;数据决定人工智能发展的天花板。深以为然。 随着ChatGPT等AIGC应用所展现出的强大能力&#xff0c;人们意识到通用人工智能的奇点正在来临&#xff0c;越来越多的企业开始涌入这条赛道。在AIGC浪潮席卷全球之际&#xff0c;数据的重要性也愈发被业界所认同…

使用ComPDFKit PDF SDK 构建iOS PDF阅读器

在当今以移动为先的世界中&#xff0c;为企业和开发人员创建一个iOS应用程序是必不可少的。随着对PDF文档处理需求的增加&#xff0c;使用ComPDFKit这个强大的PDF软件开发工具包&#xff08;SDK&#xff09;来构建iOS PDF阅读器和编辑器可以让最终用户轻松查看和编辑PDF文档。 …

嵌入式行业真的没前途吗?

就嵌入式而言&#xff0c;需要学的东西比较多。linux底层驱动的了解&#xff0c;单片机底层驱动开发、RTOS、bootloader、应用层开发&#xff08;MBD&#xff09;、各种标准、服务、协议等等&#xff0c;还要懂一些硬件&#xff0c;看得懂硬件原理图&#xff0c;最好自己能画板…

【业务功能篇53】Springboot 数据封装对象

Entity、VO、DTO解释 1&#xff09;Entity&#xff1a;实体&#xff0c;与数据库的每一行数据打交道的&#xff0c;它的属性对应数据库每个字段 class User{ private Long idCard; private String name; private Date birthday; ...... } 对应数据库的id&#xff0c;name&…

VMware虚拟机安装VMware tools

一、挂载光驱 执行以下命令来创建 /mnt/cdrom 目录&#xff1a; mkdir -p /mnt/cdrom-p 参数会确保如果 /mnt/cdrom 的上级目录&#xff08;例如 /mnt&#xff09;不存在的话也会被创建。 然后&#xff0c;你可以再次尝试挂载光盘&#xff1a; mount /dev/sr0 /mnt/cdrom这次…

DragGAN:用崭新的方式进行图像处理

该项目的论文被SIGGRAPH 2023 收录&#xff0c;论文以 StyleGAN2 架构为基础&#xff0c;实现了 “Drag” 关键点就能轻松 P 图的效果。 https://github.com/XingangPan/DragGAN https://vcai.mpi-inf.mpg.de/projects/DragGAN/ 目录 原图1测试一测试二测试三 原图2测试一测试…

基于Java+SpringBoot+vue前后端分离师生健康信息管理系统设计实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

数据结构 | 基本数据结构——栈

目录 一、线性数据结构 二、栈 2.1 何谓栈 2.2 栈抽象数据类型 2.3 用Python实现栈 2.4 匹配括号 2.5 普通情况&#xff1a;匹配符号 2.6 将十进制数转换成二进制数 3.7 前序、中序和后序表达式 3.7.1 从中序到后序的通用转换法 3.7.2 计算后序表达式 一、线性数据结…

Docker 镜像操作

Docker镜像操作 我们已经介绍了容器操作,今天来了解下 Docker镜像 以及 镜像操作 。让我们一起开启镜像之旅吧。 Docker镜像 镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库…

QML 往图表添加文字显示

需求&#xff1a; 需要在图表的某几个点上&#xff0c;添加相应的文字显示。效果如下: 主要是&#xff0c;如何将坐标进行转化为图表的相对坐标&#xff0c;然后动态创建文本后&#xff0c;将转换坐标设置到Text中。 演示demo。需要点击Text按钮后&#xff0c;图表显示。 impo…

如何为WordPress博客网站配置自己购买的域名,并且公网可访问?

文章目录 如何为WordPress博客网站配置自己购买的域名&#xff0c;并且公网可访问&#xff1f;前置条件&#xff1a;具体操作步骤如下&#xff1a;步骤1: 后台预留自定义域名步骤2: 配置您的域名DNS解析步骤3: 测试域名解析步骤4: 在前台终端测试运行步骤4: 修改cpolar配置文件…

等保测评需要做几次?做一次以后还需要做吗?

虽然我国等保政策已经严格落地执行了&#xff0c;但不少企业对于等保测评相关政策还不是很了解&#xff0c;有人在问&#xff0c;等保测评需要做几次&#xff1f;做一次以后还需要做吗&#xff1f;今天我们就来简单回答一下吧&#xff01; 等保测评需要做几次&#xff1f;做一…