iOS——KVC(键值编码)

news/2024/7/20 22:59:54 标签: ios, objective-c

键值编码(KVC)

KVC(Key Value Coding)是一种允许以字符串形式间接操作对象属性的方式。
最基本的KVC是由NSKeyValueCoding协议提供支持,最基本的操作属性如下:

  • setValue: 属性值 forKey: 属性名:为指定属性设置值;
  • valueForKey: 属性名:获取指定属性的值
    代码演示:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface AUser : NSObject

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

@end

NS_ASSUME_NONNULL_END


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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser *aUser = [[AUser alloc] init];
        //使用KVC方式为str1属性设置值
        [aUser setValue:@"astr11" forKey:@"str1"];
        //使用KVC方式为str2属性设置值
        [aUser setValue:@"astr22" forKey:@"str2"];
        //使用KVC方式获取AUser对象的属性值
        NSLog(@"str1: %@", [aUser valueForKey:@"str1"]);
        NSLog(@"str2: %@", [aUser valueForKey:@"str2"]);
    }
    return 0;
}

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

在使用KVC时,都是通过字符串来指定被操作的属性。即使用forKey传入属性名的字符串。

对于setValue: forKey: 方法,底层的执行机制如下:

  1. 程序优先考虑调用属性的setter方法
  2. 如果该类没有setter方法,KVC机制会搜索该类中名为传入的“_该字符串”的成员变量(大部分时候即创建属性的时候自动创建的成员变量)无论该成员变量是在接口或者实现部分定义、无论它用哪个访问控制符修饰,这条KVC底层上是对该成员变量的赋值。
    如果该类即没有setter方法也没有“_name”成员变量,那么KVC机制会搜索该类中名为name的成员变量(大部分时候即我们自己定义的成员变量)(与上条一样)
    如果上面3步都没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,该方法的实现就是引发一个异常,导致程序结束
    valueForKey方法其他与上面一样,但是它获取的是getter方法的返回值。没有找到成员变量会执行valueForUndefinedKey:方法,该方法也会引起异常导致程序关闭。
    代码举例:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

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

@end

NS_ASSUME_NONNULL_END


#import "AUser.h"

@implementation AUser {
    int age;
}
@end


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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser *aUser = [[AUser alloc] init];
        //使用KVC给属性赋值,KVC的搜素顺序为:
        //1、setName方法;2、_name成员变量;3、name成员变量
        //因此,在此处我们是先搜索到了_name成员变量,所以是给_name赋了值,name没有赋值
        //因此name为空
        [aUser setValue:@"strName1" forKey:@"name"];
        NSLog(@"name = %@", aUser->name);
        NSLog(@"_name = %@", aUser->_name);
        //虽然age成员变量是在实现部分定义的,但是它还是会被赋值
        [aUser setValue: [NSNumber numberWithInt:5] forKey:@"age"];
        NSLog(@"age = %@", [aUser valueForKey:@"age"]);
    }
    return 0;
}

处理不存在的Key

前面说过,使用KVC时,如果该属性没有setter、getter方法,也不存在对应的成员变量时,程序会调用setValue: forUndefinedKey:或valueForUndefinedKey:方法。系统默认该方法的实现是引发一个异常然后结束程序,但是我们可以重写这个方法,使其达到我们想要的效果。
只需要在FKApple类实现部分重写setValue:forUndefinedKey:方法,甚至不需要在类接口
声明该方法,例:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

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

@end

NS_ASSUME_NONNULL_END
#import "AUser.h"

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

@end
#import <Foundation/Foundation.h>
#import "AUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser *aUser = [[AUser alloc] init];
        [aUser setValue:@"strName1" forKey:@"1"];
        NSLog(@"name = %@", aUser->name);
        NSLog(@"_name = %@", aUser->_name);
    }
    return 0;
}

处理nil值

假如我们在一个类中定义两个属性,一个属性是NSString类型的,一个属性是int类型的。当我们给两个属性赋nil值时,NSString属性是可以被赋nil值的,而int类型的值被赋nil时会引发异常,是由于int类型的属性不能接受nil值所导致的。
也就是说,当程序尝试给某个属性设置nil值时,如果该属性并不能接受nil值,那么程序会自动执行该对象的setNilValueForKey:方法。我们同样可以重写该方法来达到我们想要的效果。例如,接下来我们重写该方法,定义一个int类型的属性age,重写该方法使得如果给age属性赋nil值时,就将age赋值为0。代码:

#import "AUser.h"

@implementation AUser {
    int age;
}

- (void) setNilValueForKey:(NSString *)key {
    //如果尝试将key为name的属性设置为nil
    if ([key isEqualToString:@"age"]) {
        //将_name设置为0
        age = 0;
    } else {
        //回调父类的setNilValueForKey:执行默认行为
        [super setNilValueForKey:key];
    }
}

@end
#import <Foundation/Foundation.h>
#import "AUser.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser *aUser = [[AUser alloc] init];
        //使用KVC给age属性传nil
        [aUser setValue: nil forKey:@"age"];
        NSLog(@"age = %@", [aUser valueForKey:@"age"]);
    }
    return 0;
}

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

key路径

KVC除了可以操作对象的额属性之外,还可以操作对象的“复合属性”。所谓“复合属性”,KVC机制将其称为key路径。例如:AUser类里面包含着一个BUser类型的bUser属性,bUser对象中又包含着b1属相和b2属性,那么KVC可以通过bUser.b1、bUser.b2这种key路径来支持操作AUser对象的bUser属性的b1和b2属性。
根据key路径设置属性值的方法:

  • setValue: forKeyPath:根据key路径设置属性值
  • valueForKeyPath: 根据key路径获取属性值
    代码示例:
    AUser:
#import <Foundation/Foundation.h>
#import "BUser.h"

NS_ASSUME_NONNULL_BEGIN

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

@property (nonatomic, assign) int aNumber;

@end

NS_ASSUME_NONNULL_END

BUser:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface BUser : NSObject

@property (nonatomic, copy) NSString *b1;
@property (nonatomic, copy) NSString *b2;

@end

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AUser *aUser = [[AUser alloc] init];
        [aUser setValue:@"12" forKey:@"aNumber"];
        [aUser setValue:[[BUser alloc] init] forKey:@"bUser"];
        [aUser setValue:@"这是b1" forKeyPath:@"bUser.b1"];
        [aUser setValue:@"这是b2" forKeyPath:@"bUser.b2"];
        NSLog(@"aNumber: %@", [aUser valueForKey:@"aNumber"]);
        NSLog(@"b1: %@", [aUser valueForKeyPath:@"bUser.b1"]);
        NSLog(@"b2: %@", [aUser valueForKeyPath:@"bUser.b2"]);
    }
    return 0;
}

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

实际上,通过KVC操作对象的性能比通过getter、setter方法操作的性能更差,使用KVC的优点是编程更加灵活,更适合提炼一些通用性质的代码


http://www.niftyadmin.cn/n/5051106.html

相关文章

第五章:C语言的数组

文章目录 1、数组的理解2、各类数组的定义3、变长数组4、字符数组 1、数组的理解 一维数组&#xff1a;比如定义一个int a[3];,那么可以将其看成两部分&#xff0c;a【3】为①&#xff0c;int为②。意思就是有一个数组名字为a&#xff0c;里面包含3个&#xff08;池&#xff0…

干细胞液氮容器选择与使用

干细胞液氮容器的使用非常重要&#xff0c;以确保干细胞样品在冷冻和储存过程中的有效性和安全性。以下是使用干细胞液氮容器时需要注意的事项&#xff1a; 1、容器选择&#xff1a;选择合适的容器非常重要。容器应具有良好的密封性能和耐腐蚀性&#xff0c;以避免外部空气和污…

linux 如何取整点前一小时的半点

要取整到前一小时的半点,可以使用date命令和一些基本的Linux命令来实现。以下是一个示例的Shell脚本: #!/bin/bash# 获取当前时间的小时和分钟 current_hour=$(date +%H) current_minute=$(date +%M)# 计算前一小时的半点 if [ $current_minute -ge

js自带的字体图标

let body document.querySelector(body)body.width 100%for (let i 1; i < 10000; i) {let str &#i;let sm str.big()let st document.createElement(span)st.innerHTML smst.style.fontSize 30pxbody.appendChild(st)} 结果如下

哈希表的模拟实现

unordered_set: 接口函数&#xff1a; 对应的应用&#xff1a; unrodered_map: 对应的函数接口&#xff1a; 对应的应用&#xff1a; 比较set和unordered_set的效率&#xff1a; 可以看到各个方面hashset是优于set的。 哈希表的模拟实现&#xff1a; 哈希表的实现分为两种&…

【Java基础-JDK21新特性】它发任它发,我用java8

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

string类模拟实现——C++

一、构造与析构 1.构造函数 构造函数需要尽可能将成员在初始化列表中初始化&#xff0c;string类的成员这里自定义的和顺序表相似&#xff0c;有_str , _size , _capacity , 以及一个静态成员 npos &#xff0c;构造函数这里实现两种&#xff0c;一种是传参为常量字符串的&am…

Git 精简快速使用

安装 Git 忽略&#xff0c;自行搜索 新建项目&#xff0c;或者在仓库拉取项目&#xff0c;进入到项目目录 Github 给出的引导&#xff0c;新项目和旧项目 echo "# testgit" >> README.md git init git add README.md git commit -m "first commit"…