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


#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的优点是编程更加灵活,更适合提炼一些通用性质的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值