「iOS」——KVC

news2024/12/26 13:31:03

iOS学习

  • 前言
  • KVC模式
    • KVC设值
    • KVC取值
    • KVC使用keyPath
    • KVC处理异常
      • 处理不存在的key
      • 处理nil异常
    • KVC处理字典
    • KVC高阶消息传递
  • 总结

前言

对KVC模式的简单学习和总结。


KVC模式

KVC(Key-Value Coding,键值编码)是一种通过字符串来访问对象属性的机制,允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。可以在运行时动态地访问和修改对象的属性。而不是在编译时确定。

以下是KVC的常用方法:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (void)setValue:(id)value forKey:(NSString *)key;//通过key设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值
- (id)valueForKey:(NSString *)key;//通过key获取值

KVC设值

我们通过- (void)setValue:(id)value forKey:(NSString *)key;方法来为KVC设值,下面给出代码演示:

#import <Foundation/Foundation.h>

@interface AUser : NSObject

@property (nonatomic, copy) NSString *str1;
@property (nonatomic, copy) NSString *str2;

@end

#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];
        [user setValue:@"Astr1" forKey:@"str1"];
        [user setValue:@"Astr2" forKey:@"str2"];
        
        NSLog(@"str1:%@",[user valueForKey:@"str1"]);
        NSLog(@"str2:%@",[user valueForKey:@"str2"]);
         
    }
    return 0;
}

运行结果为:
请添加图片描述
那么KVC设置的逻辑原理是什么呢?

请添加图片描述

如上图所示:

  1. 首先会按照setKey、_setKey的顺序查找方法,如找到方法,则直接调用方法并赋值;
  2. 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly(是否可以直接访问成员变量,默认返回YES);
  3. 若accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出NSUnknowKeyExpection异常;
  4. 若accessInstanceVariablesDirectly方法返回NO,那么就会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常;

我们来验证一些处理逻辑:

#import <Foundation/Foundation.h>

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


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:@"strName1" forKey:@"_name"];
        [aUser setValue:@"strName2" forKey:@"name"];
        
        NSLog(@"name = %@", aUser->name);
        NSLog(@"_name = %@", aUser->_name);
        
    }
    return 0;
}

请添加图片描述

KVC取值

我们通过- (id)valueForKey:(NSString *)key; 方法来获取值。
下面来探究取值顺序:



#import "AUser.h"

@implementation AUser

-(int) getAge{
    return 9999;
}

-(int) age
{
    return 999;
}

-(int) isAge
{
    return 99;
}
@end


#import <Foundation/Foundation.h>
#import "AUser.h"
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *user = [[AUser alloc] init];

        [user setValue:@"9" forKey:@"age"];
        
        NSLog(@"age:%@",[user valueForKey:@"age"]);
        
    }
    return 0;
}

运行后:

请添加图片描述
注释掉getAge后:

请添加图片描述
注释掉age方法后:
请添加图片描述
再注释掉isAge方法后:
请添加图片描述

原理:

  1. 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
  2. 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出NSUnknowKeyExpection异常;
  3. 若返回的YES,则按照_ key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
  4. 找不到则调用valueForUndefinedKey:抛出NSUnknowKeyExpection异常;

请添加图片描述

KVC使用keyPath

面对复杂的嵌套属性进行初始化赋值时,如果使用key一层层赋值十分麻烦。我们可以采用keyPath来访问对象的嵌套属性。
keyPath的两个方法:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath设置值
- (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath获取值

下面我们来以代码举例:
我们先创建一个BUser:

#import <Foundation/Foundation.h>

@interface BUser : NSObject
@property (nonatomic, copy) NSString *strb1;
@property (nonatomic, copy) NSString *strb2;
@end

我们再创建一个AUser嵌套一个BUser:

#import <Foundation/Foundation.h>
#import "BUser.h"

@interface AUser : NSObject
{
    BUser *bUser;
}
@end

此时,AUser对象中,含有一个嵌套属性,我们可以使用keyPath进行为嵌套对象赋值并且取值:



#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        AUser *aUser = [[AUser alloc] init];
        BUser *bUser = [[BUser alloc] init];
        [aUser setValue:bUser forKey:@"bUser"];
        [aUser setValue:@"b1" forKeyPath:@"bUser.strb1"];
        [aUser setValue:@"b2" forKeyPath:@"bUser.strb2"];
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb1"]);
        NSLog(@"b1:%@",[aUser valueForKeyPath:@"bUser.strb2"]);

    }
    return 0;
}

打印结果:
请添加图片描述

KVC处理异常

处理不存在的key

在上面我们说过KVC设置的顺序。如果最后没有找到相应的成员变量,则会调用setValue:forUndefinedKey:并抛出NSUnknowKeyExpection异常来结束程序。
我们只需要重写- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key 这个方法,则不会产生Crash。


#import "AUser1.h"
@implementation AUser1{
    int age;
}

- (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key {
    NSLog(@"重写了setValue:value forUndefinedKey方法");
}
@end

#import <Foundation/Foundation.h>
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        AUser1 *aUser = [[AUser1 alloc] init];
        //处理不存在的key
        [aUser setValue:@"strName1" forKey:@"1"];
        
    }
    return 0;
}

运行结果:
请添加图片描述

处理nil异常

我们可以将nil赋值给字符串类型,但是不能复制给int或NSInteger类型。如果需要为对象赋nil时,则需要自己处理一下nil异常的部分,例如给int类型赋nil的情况。
当我们给int类型赋值nil时,就会出现异常,会执行-(void)setNilValueForKey:(NSString *)key这个方法,使程序崩溃,所以我们通常需要重写这个方法来处理nil异常。



#import "AUser1.h"

@implementation AUser1{
    int age;
}

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


#import <Foundation/Foundation.h>
#import "AUser1.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser1 *aUser = [[AUser1 alloc] init];
        
        [aUser setValue:nil forKey:@"age"];
        
        NSLog(@"age = %@", [aUser valueForKey:@"age"]);
    }
    return 0;
}

运行结果:

请添加图片描述

KVC处理字典

我们可以通过字典进行批量的设值取值操作。

  • setValuesForKeysWithDictionary: 方法用于将字典中的值赋给对象的属性,
  • dictionaryWithValuesForKeys: 方法则用于根据属性键数组获取对象的属性值并返回对应的字典。
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, assign) NSInteger age;

@end

#import <Foundation/Foundation.h>
#import "Person.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person* person = [[Person alloc] init];
        //使用KVC批量存值
        [person setValue:@"Kobe" forKey:@"name"];
        [person setValue:@"man" forKey:@"sex"];
        [person setValue:@"24" forKey:@"age"];
        NSDictionary* firstDictionary = [person dictionaryWithValuesForKeys:@[@"name", @"sex", @"age"]];
        NSLog(@"dictonary = %@", firstDictionary);
        
        //使用KVC批量赋值
        NSDictionary* secondDictionary = @{@"name":@"瑞娜", @"age":@2, @"sex": @"woman"};
        Person* secondPerson = [[Person alloc] init];
        [secondPerson setValuesForKeysWithDictionary:secondDictionary];
        NSLog(@"name  = %@, age = %ld, sex = %@", secondPerson.name, secondPerson.age, secondPerson.sex);
        
    }
    return 0;
}

KVC高阶消息传递

通俗来讲就是让数组中的每一个元素都执行某个方法,并把结果返回到新的数组中。这里我们实现将数组中的每个首字母大写,并且返回长度。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSArray* arrStr = @[@"reyna",@"jett",@"Neon"];
        NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"];
        for (NSString* str  in arrCapStr) {
            NSLog(@"%@",str);
        }
        NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
        for (NSNumber* length  in arrCapStrLength) {
            NSLog(@"%ld",(long)length.integerValue);
        }
        
    }
    return 0;
}

运行结果:

请添加图片描述


总结

KVC相比于setter和getter方法,虽然在性能上差一点,但是在编码上更加的灵活,简洁,可以批量操作并且可以在运行时动态地访问和操作对象的属性。

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

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

相关文章

详解Linux文件系统

先来研究一下磁盘&#xff1a; LBA 磁头面数 所有磁头是共进退的。 每个扇区的大小假设为&#xff1a;512 磁盘的总容量&#xff1a;盘面*t*s*512 数组的下标范围&#xff1a;盘面*t*s LBA&#xff1a;xxx 假设xxx为LBA地址 xxx/1000h 及为h号盘面 xxx/100c 及c号磁道…

国产化框架PaddleYOLO结合Swanlab进行作物检测

1. 项目介绍 粮食安全&#xff0c;作为人类生存与发展的基石&#xff0c;始终是全球关注的焦点。它不仅仅关乎粮食的充足供应&#xff0c;更涉及粮食的质量安全、营养健康以及可持续生产等多个维度。在全球化、气候变化和资源环境约束日益加剧的背景下&#xff0c;如何确保粮食…

ComfyUI物品移除讲解

实现效果 一、下载插件 comfyui inpaint nodes 提供重绘功能节点&#xff0c;效果比自带的好很多 官方介绍 使用ComfyUI更好地进行修补的节点&#xff1a;用于SDXL&#xff0c;LaMa&#xff0c;MAT和各种其他工具的Foocus修补模型&#xff0c;用于预填充修补和去除区域。 插件…

Android调用科大讯飞语音转写 API以及解析踩坑之旅

需求 需要对本地音频文件&#xff0c;调用科大讯飞的api进行转文字&#xff0c;本来呢&#xff0c;以为很简单&#xff0c;结果坑不少。 语音转写 API 文档 坑1&#xff1a;解析 下载demo&#xff0c;代码也挺简单&#xff0c;放到idea中&#xff0c;替换一下key&#xff0c…

基于Springboot+Vue的网上书店(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 在这个…

用户体验测试——21条UX设计原则

根据心理学的研究和发展,对应理论不断被完善到用户体验设计领域,以下21条UX设计原则即为心理学在用户体验设计中的应用,在设计和测试中应用这些原则,可以提高用户体验质量特性,如下所示: 21条UX设计原则 有效性 特斯勒定律(最小复杂度定律) 这个定律也被称为“复杂性…

数据转换新利器,开启企业高效之路

Maria是 S 公司的采购专员&#xff0c;最近需要进行公司设备的采购&#xff0c;这可让她犯了难&#xff1a;公司多个部门提交采购申请&#xff0c;但每个部门都有着不同的需求。一时间她就收到了大量的申请单&#xff0c;却难以高效整合处理&#xff08;比如哪些申请可以合并采…

环境变量

见一见环境变量&#xff1a; 查看环境变量的命令 命令行&#xff1a; echo &PATH echo &HOME env ps ajx ps aux ps -f -o pid code.c中获取环境变量 main&#xff08;char* env[]&#xff09; char* getenv&#xff08;env_name&#xff09; 本地变量 shell…

App模拟下载场景的demo

摘要 目的&#xff1a;提供一个稳定的下载场景&#xff0c;可以手动触发和定时触发下载&#xff0c;每次下载相同大小文件&#xff0c;研究下载场景的功耗影响 原理&#xff1a;把电脑当做服务器&#xff0c;手机测试App固定下载电脑存放的某个XXXMB的大文件&#xff0c;基于…

如何使用ssm实现校园体育赛事管理系统的设计与实现+vue

TOC ssm713校园体育赛事管理系统的设计与实现vue 绪论 课题背景 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化。目前&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得…

PE节表中是否存在misc.VirtualSize 比SizeofRawData还要大的情况

确实是存在的,这是win10自带记事本,可以看到 确实是大.所以在申请imagebuffer的时候,还是需要比较大小.但是在还原的时候.只考虑sizeofRawData即可>

【Android 14源码分析】WMS-窗口显示-第一步:addWindow

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

项目管理专业资质认证ICB 3中关于项目经理素质的标准

项目管理专业资质认证ICB 3中关于项目经理素质的标准&#xff0c;的确很全面&#xff0c;下面摘录之&#xff1a;

三款专业的英文文献翻译工具,翻译论文不在话下

阅读英文论文文献时免不了要借用一些翻译软件来帮助理解&#xff0c;但因为论文文献的特殊性&#xff0c;普通的翻译软件不能很好的翻译一些专业名词和术语&#xff0c;所以这里给大家分享三款可以胜任文献翻译的专业翻译工具&#xff0c;可以快速准确的完成英文文献翻译工作。…

CDGA|2024年数据治理的六个关键建议

随着数字经济的快速发展&#xff0c;数据已成为企业运营和决策的核心资产。在2024年&#xff0c;做好数据治理对于提升企业的竞争力和运营效率至关重要。以下是六个关键建议&#xff0c;帮助企业有效应对数据治理的挑战。 1. 制定明确的数据治理策略 首先&#xff0c;企业需要…

遥感影像-实例分割数据集:iSAID 从切图到YOLO格式数据集制作详细介绍

背景介绍 开源数据集isaid标注包含实例分割&#xff0c;但是原始影像太大&#xff0c;很吃显存&#xff0c;一般显卡无法用原始影像直接训练&#xff0c;所以需要对影像进行裁剪&#xff0c;并生成对应的标签&#xff0c;因为想用yolo系列跑模型&#xff0c;所以将标签需要转为…

【设计模式-模板】

定义 模板方法模式是一种行为设计模式&#xff0c;它在一个方法中定义了一个算法的骨架&#xff0c;并将一些步骤延迟到子类中实现。通过这种方式&#xff0c;模板方法允许子类在不改变算法结构的情况下重新定义算法中的某些特定步骤。 UML图 组成角色 AbstractClass&#x…

Java 为什么使用 UTF-16 而不是更节省内存的 UTF-8?

Java 选择 UTF-16 编码而不是更节省内存的 UTF-8 这一决定&#xff0c;涉及多个层面的设计权衡&#xff0c;包括历史原因、虚拟机&#xff08;JVM&#xff09;实现的复杂度、性能和字符处理的一致性。要理解这个问题&#xff0c;我们需要从 Java 语言的设计初衷、JVM 的工作机制…

C++:笔试题

1.什么是虚函数&#xff1f;什么是纯虚函数&#xff1f; 虚函数是类中的一个成员函数&#xff0c;使用关键字virtual在函数名前声明。 虚函数主要目的是允许子类重写父类中的同名函数&#xff0c;从而实现多态性&#xff0c;并且子函数重写的是虚函数表中的函数。 当通过父类的…

七、添加攻击音效

一、添加动画事件 1、在动画事件中添加音效 2、添加音频组件 3、代码 public void PlayAttackSound() {AudioSource1.PlayOneShot(AudioClip1, SoundValue);//PlayOneShot播放一个音频剪辑&#xff08;AudioClip&#xff09;一次 }