【iOS】--KVC与KVO

news2024/11/29 20:35:01

键值编码(KVC)与键值监听(KVO)

KVC(Key Value Coding)允许以字符串的形式间接操作对象的属性。

简单的KVC

最基本的KVC由NSKeyValueCoding协议提供支持,最基本的操作属性的两个方法如下

  • setVaule:属性值 forKey:为制定属性设置值
  • valueForKey:属性名:获取指定属性的值

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *pass;
@property (nonatomic, copy) NSDate *birth;
@end

NS_ASSUME_NONNULL_END


//

#import <Foundation/Foundation.h>
#import "FKUser.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        FKUser *user = [[FKUser alloc] init];
        [user setValue:@"花城" forKey:@"name"];
        [user setValue:@"1155" forKey:@"pass"];
        [user setValue:[[NSDate alloc] init] forKey:@"birth"];
        NSLog(@"user的name为%@", [user valueForKey:@"name"]);
        NSLog(@"user的pass为%@", [user valueForKey:@"pass"]);
        NSLog(@"user的birth为%@", [user valueForKey:@"birth"]);
        
    }
    return 0;
}

请添加图片描述
上面的代码先创建一个user对象,在对对象的属性通过KVC方式设置属性值,最后通过KVC方式获取指定属性的值
在KVC编程方式中,无论调用setValue:forKey:方法还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名

setValue:属性值forKey@“name”代码

  • 程序优先考虑调用“setName:属性值”;代码通过setter方法完成设置
  • 如果该类没有setName:方法,那么KVC机制回搜索该类中名为_name的成员变量,无论该成员变量是在类接口部分定义还是在类实现部分定义,也无论用哪个访问权限控制符修饰,这条KVC代码底层实际上就是对_name成员变量赋值
  • 如果该类即没有setName:方法,也没有定义_name成员变量,那么KVC机制回搜索该类中名为name的成员变量,无论该成员变量是在类接口部分定义还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对name成员变量赋值。
  • 如果上边三步都没有找到,那么系统会执行该对象的setValue:forUndefinrdKey:方法
    默认的valueForUndefinedKey:方法实现就是引发一个异常,这个异常将会导致程序因为异常而结束。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKDog : NSObject {
    @package
    NSString *name;
    NSString *_name;
}

@end

NS_ASSUME_NONNULL_END
//

#import "FKDog.h"

@implementation FKDog {
    int age;
}
@end




        FKDog *dog = [[FKDog alloc] init];
        [dog setValue:@"戚容" forKey:@"name"];
        NSLog(@"dog->name = %@", dog->name);
        NSLog(@"dog->_name = %@", dog->_name);
        [dog setValue:[NSNumber numberWithInt:800] forKey:@"age"];
        NSLog(@"dog的age%@", [dog valueForKey:@"age"]);

请添加图片描述
我们先对dog的name属性进行赋值,但是由于FKDog类并不存在setName:方法,因此我们对dog的成员变量_name进行赋值。我们输出dog->name时,可以看到输出“戚容”。所以我们输出dog->name时为空,而输出dog->_name时不为空。
最后我们通过KVC对dog的age属性赋值并访问

处理不存在的key

当我们使用KVC方式操作属性时,这些属性可能并不存在,即不存在对应的setter,getter方法,也不存在对应的成员变量,KVC会自动调用setValue:forUndefinedKey:和valueForUndefinedKey:方法,但是系统默认实现的这两个方法仅仅是引发异常,并没有进行任何特别处理

//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKApple : NSObject

@end

NS_ASSUME_NONNULL_END



//

#import "FKApple.h"

@implementation FKApple

@end



FKApple *apple = [[FKApple alloc] init];
        [apple setValue:@"monster" forKey:@"name"];
        [apple valueForKey:@"name"];
        

请添加图片描述
提示说程序尝试设定的name对象不存在,因此引发异常。

这句提示就是setValue:forUndefinedKey方法的默认实现,这个实现引发一个NSUnknownKeyExpection异常,并结束程序,这个默认方法的实现可能并不适合实际,可能我们不想让他结束。这时候就要重写这个方法。

//

#import "FKApple.h"

@implementation FKApple
- (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key{
    NSLog(@"这个key不存在,你是不是敲错了?");
    NSLog(@"你尝试设定的value为:%@", value);
}
@end

请添加图片描述
显而易见,我们通过KVC设置对象的属性时没有发生任何异常,那为什么还是崩了呢?
因为还有这一句代码

[apple valueForKey:@“name”];

我们尝试通过KVC访问对象的name key,因为我们不存在name方法,也不存在name,_name成员变量,因此KVC会调用valueForUndefinedKey:方法,这时我们重写这个方法。
请添加图片描述


#import "FKApple.h"

@implementation FKApple
- (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key{
    NSLog(@"这个key不存在,你是不是敲错了?");
    NSLog(@"你尝试设定的value为:%@", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
    NSLog(@"这个key不存在,你是不是敲错了?");
    return key;
}
@end

可以发现重写之后就没有异常了。
当KVC操作不存在的key时,KVC机制总是调用重写过的方法进行处理,通过这种处理机制,可以非常方便的定制自己的处理行为。

处理nil值

当通过KVC设置对象的属性时,如果属性是基本类型(int, float,doule),并且我们给他一个对应的属性值,那么程序可以正确的进行设置,但是如果我们为基本类型的属性设置一个nil,会发生什么事情呢,KVC会把这个东西当作0还是1?

//  Created by 王璐 on 2023/5/13.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKItem : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int price;
@end


NS_ASSUME_NONNULL_END



FKItem *item = [[FKItem alloc] init];
        [item setValue:nil forKey:@"name"];
        [item setValue:nil forKey:@"price"];
        NSLog(@"item的name为%@", [item valueForKey:@"name"]);
        NSLog(@"item的price为%@", [item valueForKey:@"price"]);
        
        

我们设置了两个属性,一个是NSString类型,它可以接受nil,另一个是int类型,不能接受nil。
请添加图片描述
提示我们引发了一个NSInvidalArguementExpection异常。这是因为int类型的值不能接受nil造成的
这段提示信息是由程序中的setNilValueForKey:方法所产生的。也就是说,当程序尝试为某个属性值设置nil值时,如果该属性并不接受nil值,那么程序会自动执行该属性的setNilValueForKey:方法。我们重写一下这个方法。

//

#import "FKItem.h"

@implementation FKItem
- (void)setNilValueForKey:(NSString *)key{
    if ([key isEqualToString:@"price"]) {
        _price = 0;
    } else {
        [super setNilValueForKey:key];
    }
}
@end

请添加图片描述
如果我们尝试将key为price的属性设置为nil,那么程序将会直接将_price成员变量设置为0,说明FKItem的price属性会把nil当成0处理,设置其他属性则不做处理。

key路径

KVC除了可操作对象的属性外,还可以操作对象的复合属性。所谓复合属性,KVC将其称为key路径。比如KFOrder对象内包含一个FKItem的类型的属性item,FKitem又包含了name属性和price属性,那么KVC就可以通过item.name,item.price这种key路径来操作FKOrder对象中的item属性和price属性
在KVC中操作key路径的方法如下。

  • setValue:forKeyPath:根据key路径设置属性值
  • valueForKeyPath:根据key路径获取属性值

请添加图片描述


#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKOrder : NSObject
@property (nonatomic, strong) FKItem *item;
@property (nonatomic, assign) int amount;
- (int)totalPrice;
@end

NS_ASSUME_NONNULL_END



FKOrder *order = [[FKOrder alloc] init];
        [order setValue:@"5000" forKey:@"amount"];
        [order setValue:[[FKItem alloc] init] forKey:@"item"];
        [order setValue:@"书包" forKeyPath:@"item.name"];
        [order setValue:[NSNumber numberWithInt:5] forKeyPath:@"item.price"];
        NSLog(@"订单包含%@个%@, 总价为%@",[order valueForKey:@"amount"], [order valueForKeyPath:@"item.name"], [order valueForKey:@"totalPrice"]);
        

setValue:forKey:,valueForKey:设置复合属性并获取复合属性的值,这样就可以直接操作FKOreder对象中的item属性的name属性了,也可以直接操作FKOreder对象中item属性的price属性
OC的集合同样支持使用KVC进行整体操作
为什么要用KVC来操作呢?使用setter,getter不行吗?是不是这样效率比较高呢?实际上,通过KVC操作的性能比setter,getter更差,使用KVC的优势在于更灵活,更适合提炼一些通用性质的代码。因为KVC的方式允许通过字符串的方式来操作对象的属性,这个字符串既可以是常量,也可以是变量。因此具有极高的灵活性。

KVO(键值坚听)

在iOS应用开发过程中,iOS应用通常会把应用程序组件分开成为数据模型组件和视图组件,其中数据模型组件负责维护应用程序的状态数据,视图组件负责显示数据模型组件内部的状态数据。

我们的需求是:在数据模型组件的状态数据发生改变时,视图组件能动态的更新自己,及时显示数据模型组件更新后的数据。
在这里插入图片描述
请添加图片描述

//

#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKItemView : NSObject
@property (nonatomic, weak) FKItem *item;
- (void)showItemInfo;
@end

NS_ASSUME_NONNULL_END

为了能让FKItemView监听得到FKItem组件的状态改变,我们需要为FKItem组件添加一个监控属性改变的监听器。并重写监听器的observerValueForKeyPath:change:context:方法

//

#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKItemView : NSObject
@property (nonatomic, weak) FKItem *item;
- (void)showItemInfo;
@end

NS_ASSUME_NONNULL_END



//

#import "FKItemView.h"

@implementation FKItemView
- (void)showItemInfo {
    NSLog(@"物品名称为%@, 物品价格为%d", self.item.name, self.item.price);
}
- (void)setItem:(FKItem *)item {
    self->_item = item;
    [self.item addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    [self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil];
    
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"bserveValueForKeyPath");
    NSLog(@"keyPath:%@", keyPath);
    NSLog(@"object:%@", object);
    NSLog(@"change objectForKey:%@", [change objectForKey:@"new"]);
    NSLog(@"change:%@", context);
}
- (void)dealloc {
    [self.item removeObserver:self forKeyPath:@"name"];
    [self.item removeObserver:self forKeyPath:@"price"];
}
@end



FKItem *item = [[FKItem alloc] init];
        item.name = @"花城";
        item.price = 800;
        FKItemView *view = [[FKItemView alloc] init];
        view.item = item;
        [view showItemInfo];
        item.name = @"huacheng";
        item.price = 10000;
        

请添加图片描述
我们可以看到监听器监听方法的输出,可以看出监听器完全可以得到我们修改的属性,接下来我们把这些修改的属性加到UI上就好了。

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

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

相关文章

利用具有局部信息的引导自注意进行息肉分割

文章目录 Using Guided Self-Attention with Local Information for Polyp Segmentation摘要本文方法PP-Guided Self-AttentionLocal-to-Global Mechanism损失函数 实验结果 Using Guided Self-Attention with Local Information for Polyp Segmentation 摘要 背景 自动准确的…

Microsoft Edge是一款现代化的浏览器,它拥有众多功能和强大的性能

随着互联网的不断发展&#xff0c;浏览器已经成为我们日常生活中必不可少的工具之一。作为一款现代化的浏览器&#xff0c;Microsoft Edge拥有许多强大的功能&#xff0c;为用户带来更加流畅的浏览体验。最近&#xff0c;Edge推出了分屏功能&#xff0c;这项功能可以大大提高生…

STM32------ADC

ADC 1、ADC介绍 1、1介绍 ADC即模数转换器。 例如&#xff1a;电信号转化成数字量。 1、2 ADC类型 并联比较型、逐次逼近型 并联比较型如下&#xff1a; 分压部分比较部分编码部分。 ADC分辨率&#xff1a;三位&#xff08;上图&#xff09; 优点&#xff1a;转换速度快…

类和对象下

文章目录 一、初始化列表1、语法&#xff1a;2、初始化顺序 二、static成员三、友元1、友元函数2、友元类 四、拷贝对象时的编译器优化例1、例2、例3、 一、初始化列表 1、语法&#xff1a; 初始化列表&#xff1a; 以一个冒号开始&#xff0c;接着是一个以逗号分隔的数据成员…

【Python PyTorch】零基础也能轻松掌握的学习路线与参考资料

文章目录 一、PyTorch的基础知识二、PyTorch数据处理三、PyTorch模型构建四、PyTorch模型训练和评估五、总结 作为当前最流行的深度学习框架之一&#xff0c;PyTorch已成为许多数据科学家和深度学习工程师的首选。本文将提供一条针对初学者的完整的学习路线&#xff0c;包括PyT…

hnust 湖南科技大学 2023 安卓 期中考试 复习资料

前言 ★&#xff1a;录音中提到的致谢&#xff1a;hwl&#xff0c;lqx&#xff0c;ly&#xff0c;sw重点来源&#xff1a;7-8班 PPT和录音内容来源&#xff1a;PPT知识点大多很抽象&#xff0c;需要联系实际代码来理解多做1-9章课后习题&#xff0c;编程题可以不做获取最新版本…

【AI大模型】“讯飞星火”大模型计划10月底赶超ChatGPT

文章目录 前言你使用过这种对话式AI吗&#xff1f;有什么看法或感受&#xff1f;“讯飞星火大模型将超越chatgpt&#xff1f;”这类型的人工智能对现在的社会有什么意义&#xff1f;这类型的人工智能&#xff0c;未来前景如何&#xff1f;申请体验写在最后 前言 5月6日&#xf…

第十三章 使用Postfix与Dovecot部署邮件系统

文章目录 第十三章 使用Postfix与Dovecot部署邮件系统一、电子邮件系统1、常见的邮件协议2、注意事项 二、部署电子邮件系统1、部署基础的电子邮件系统&#xff08;1&#xff09;、配置服务器主机名称&#xff08;2&#xff09;、配置服务器的DNS地址&#xff08;3&#xff09;…

【LeetCode: 115. 不同的子序列 | 暴力递归=>记忆化搜索=>动态规划 | 位置对应】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

队列Queue和栈Stack

什么是队列queue? 先进先出表,是一种受限制的线性表.其限制是仅允许在表的队尾进行插入数据和表的队头进行删除数据. 队列的使用场景: 线程池ThreadPoolExecutor ThreadPoolExecutor源码中核心变量(前面3个是重点) corePoolSize线程池维护的最小线程数量,哪怕是空闲的BlockQ…

Carla仿真三:Traffic Manager交通管理器详解

CARLA Traffic Manager 一、什么是Traffic Manager1、Traffic Manager简介2、Traffic Manager框架3、Traffic Manager模块 二、Traffic Manager控制方法1、TM控制交通参与者的自动驾驶的规则2、TM控制交通参与者行为的API摘要3、TM控制交通参与者行为的API 三、Traffic Manager…

力扣sql中等篇练习(十九)

力扣sql中等篇练习(十九) 1 苹果和桔子的个数 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 a 示例输入 b 示例输出 1.2 示例sql语句 # 没有使用group by也可以使用sum函数,因为默认是所有数据行自成一组 SELECT SUM(b.apple_countIFNULL(c.…

[LeetCode周赛复盘] 第 104 场双周赛20230513

[LeetCode周赛复盘] 第 104 场双周赛20230513 一、本周周赛总结6366. 老人的数目1. 题目描述2. 思路分析3. 代码实现 6367. 矩阵中的和1. 题目描述2. 思路分析3. 代码实现 6369. 最大或值1. 题目描述2. 思路分析3. 代码实现 6423. 英雄的力量1. 题目描述2. 思路分析3. 代码实现…

C:sh: 总是爆出:1: pause: not found

在linux平台下应该使用&#xff1a;pause() 导入&#xff1a;#include unistd.h 也就是unix下的标准函数&#xff0c;而不是代码不跨平台&#xff0c;从其他语言转来的新手很不舒服 参考&#xff1a;sh: 1: pause: not found_yjyn1的博客-CSDN博客

mysql数据库的表约束

表的约束 5.1&#xff1a;表的约束的概念 定义&#xff1a; 数据库表约束是用于定义和实施数据完整性的规则或条件。它们被应用于数据库表中的列&#xff0c;以确保数据的一致性、有效性和准确性。表约束可以强制执行特定的规则&#xff0c;限制数据的插入、更新或删除操作&…

ChatGPT的诞生和发展

ChatGPT的诞生和发展 ChatGPT是一种基于GPT模型的聊天机器人。GPT模型是一种基于深度学习的自然语言处理模型&#xff0c;由OpenAI团队开发&#xff0c;可以生成与输入文本相关的连续文本。ChatGPT的诞生和发展&#xff0c;可以追溯到GPT模型的开发与应用。 一、GPT模型的开…

jdk与tomcat的安装-----linux软件的安装与配置

安装方式 jdk的安装 上传jdk压缩包解压 tar -zxvf jdk压缩包名字 -C 解压地址配置环境变量 vi /etc/profileJAVA_HOMEjdk解压地址 PATH$JAVA_HOME/bin:$PATY重新加载profile文件&#xff0c;使更改的配置生效 source /etc/profile 查看是否安装成功 java -verisontomcat的…

Android MMKV - 性能强悍的存储工具(腾讯出品)

MMKV 是基于 mmap 内存映射的 key-value 组件&#xff0c;底层序列化/反序列化使用 protobuf 实现&#xff0c;性能高&#xff0c;稳定性强。从 2015 年中至今在微信上使用&#xff0c;其性能和稳定性经过了时间的验证. 起源 微信作为一个即时通讯项目,需要记录数据比较多.需…

基于Android studio学生考勤签到系统app

功能详细介绍 客户端 签到&#xff1a;用户再登录软件后&#xff0c;可以点击签到&#xff0c;进行在线签到&#xff0c;以记录当天的考勤信息。 请假&#xff1a;用户点击请假后&#xff0c;可以在线申请请假&#xff0c;等待教师的查看审核。 我的班级&#xff1a;可以查看个…

软考A计划-真题-分类精讲汇总-第七章(项目管理)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…