• iOS——KVC(键值编码)


    键值编码(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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    
    #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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

    在使用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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    
    #import "AUser.h"
    
    @implementation AUser {
        int age;
    }
    @end
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    
    #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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    处理不存在的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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    #import "AUser.h"
    
    - (void) setValue: (id)value forUndefinedKey:(nonnull NSString *)key {
        NSLog(@"重写了setValue:value forUndefinedKey方法");
    }
    
    @end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    处理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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

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

  • 相关阅读:
    Spring的 webFlux 和 webMVC
    2023第六届中国国际眼科医学技术推广大会/山东视力康复展
    macOS 系统 Kafka 快速入门
    ACL访问控制列表 基础、创建ACL访问控制列表的两种方式、配置ACL访问控制列表规则、修改ACL规则的默认步长。子网掩码、反掩码、通配符掩码的区别和作用。
    文件的上传和下载
    plsql 查询数据中文乱码
    大一学生期末大作业 html+css+javascript网页设计实例【电影购票项目】html网页制作成品代码
    OpenGL Mac开发-如何使用imgui(1.89.4)插件进行调试
    MSE 风险管理功能发布
    Kubernetes CKA 模拟题解析【2022最新版】(连载002)
  • 原文地址:https://blog.csdn.net/m0_73348697/article/details/133186675