【iOS】—— 初识KVC

KVC

KVC 是 Objective-C 中的键值编码(Key-Value Coding)的缩写。它是一种通过键(Key)来访问对象的属性值的机制,而不是直接使用属性的访问器方法或实例变量。
使用 KVC,你可以通过字符串键来获取、设置或观察对象的属性值,而不需要知道属性的具体名称或实现细节。这为动态访问和操作对象的属性提供了一种便捷的方式。

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

  1. setValue:属性值 forKey:属性名: 为指定属性设置属性值
  2. valueForKey:属性名 :获取指定属性的值

1.简单的KVC

先看下面代码:
首先定义一个FKUser的类:往类中定义3个属性。

#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

然后在实现部分通过 @synthesize对这3个属性合成getter和setter方法。
接下来通过KVC来设置FKUser对象的属性,以及访问属性。代码如下:

#import <Foundation/Foundation.h>
#import "FKUser.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKUser* user = [[FKUser alloc] init];
        [user setValue:@"孙悟空" forKey:@"name"];
        [user setValue:@"12345" 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;
}

运行结果如下:
在这里插入图片描述
  在KVC编程方式中,无论调用 setValue:forKey 方法,还是调用 valueForKey:方法,都是通过 NSString 对象来指定被操作属性的,其中 forKey:标签用于传入属性名。
对于 setValue:属性值 forKey@“name”;代码,底层的执行机制如下。

  • (1)程序优先考虑调用“setName:属性值;”代码通过 setter方法完成设置。
  • (2)如果该类没有 setName:方法, KVC 机制会搜索该类名为_name的成员变量,无论该成员变量是在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际上就是对_name成员变量赋值。
  • (3)如果该类既没有 setName:方法,也没有定义_name成员变量, KVC机制会搜索该类名为 name的成员变量,无论该成员变量是在类接口部分定义,还是在类实现部分定义,也无论用哪个访问控制符修饰,这条KVC代码底层实际就是对 name成员变量赋值。
  • (4)如果上面 3条都没有找到,系统将会执行该对象的 setValue: forUndefinedKey:方法。
    对于valueforKey@“name”;代码底层的执行逻辑和上面差不多。

这边通过代码示例:
先定义一个FKDog的类

#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

在主函数中测试程序:

#import <Foundation/Foundation.h>
#import "FKUser.h"
#import "FKDog.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建FKDog对象
        FKDog* dog = [[FKDog alloc] init];
        //使用KVC方式为name属性赋值,
        [dog setValue:@"旺财" forKey:@"name"];
        NSLog(@"dog->name:%@", dog->name);
        NSLog(@"dog->name:%@", dog->_name);
        //使用KVC对age赋值,将会导致实现部分的age成员变量被赋值
        [dog setValue:[NSNumber numberWithInt:5] forKey:@"age"];
        NSLog(@"dog->age:%@", [dog valueForKey:@"age"]);
    }
    return 0;
}

代码运行的结果如下:
在这里插入图片描述
因此可以总结出:
KVC的搜索顺序为:

  1. setName:方法
  2. _name成员变量
  3. name成员变量

2.处理不存在的key

当使用KVC方法操作属性的时候,这些属性可能并不存在——既不存在对应的setter,getter方法。也不存在对应的成员变量,KVC将会自动调用setValue:forUndefineKey:和valueForUndefineKey:方法系统默认实现的这两个方法仅仅只是引发异常,并不进行任何处理。例如下面程序定义了一个FKApple类,该类没有定义的成员变量和方法。

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKApple : NSObject

@end

NS_ASSUME_NONNULL_END
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKApple* apple = [[FKApple alloc] init];
        [apple setValue:@"大苹果" forKey:@"name"];
        [apple valueForKey:@"name"];
    }
    return 0;
}

运行结果如下:
在这里插入图片描述
上面异常信息提示:程序尝试设置的name并不存在,因此,程序引发了NSUnknownKeyException异常。

对于这种情况,可以选择在FKApple.m重写 setValue:forUndefined Key: 方法,并且再加入 - (void)valueForUndefinedKey:(id)key 方法。

#import "FKApple.h"

@implementation FKApple

- (void) setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"你尝试访问的key:【%@】并不存在!", key);
    NSLog(@"你尝试设置的value:%@", value);
}

- (void)valueForUndefinedKey:(id)key {
    NSLog(@"你访问的key:【%@】不存在",key);
}
@end

代码运行结果如下:
在这里插入图片描述

3.处理nil值

当调用KVC来设置对象的属性的时候,属性的类型为基本类型(int,float,double),如果为基本类型的属性设置一个nil,会出现什么效果呢?
接下来通过下面的代码来实现:
先定义一个FKItem的类:

#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

在主函数中给他们赋值为空。

#import "FKItem.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建FKItem对象
        FKItem* item = [[FKItem alloc] init];
        //使用KVC方法尝试为name,price属性设置为nil。
        [item setValue:nil forKey:@"name"];
        [item setValue:nil forKey:@"price"];
        
        NSLog(@"item的name为:%@", [item valueForKey:@"name"]);
        NSLog(@"item的price为:%@", [item valueForKey:@"price"]);
    }
    return 0;
}

代码运行结果如下:
在这里插入图片描述
由于int类型的price属性不接受nil的值所导致的结果。如果该属性不接受nil值那么程序将会自动执行该对象setNilValueForKey:方法。
如果为了自行定义这种行为,可以通过重写setNilValueForKey:方法来实现。下面是重写setNilValueForKey:方法。

#import "FKItem.h"

@implementation FKItem
 - (void) setNilValueForKey:(NSString *)key {
    //如果尝试将key为price的属性设置为nil。
    if ([key isEqualToString:@"price"]) {
        //将该price设置为0
        _price = 0;
    } else {
        //回调父类的setNilValueForKey,执行默认行为
        [super setNilValueForKey:key];
    }
}
@end

重新运行后的结果:
在这里插入图片描述
通过重写 setNilValueForKey: 方法,你可以在给属性赋值为 nil 时提供自定义的行为,以避免程序崩溃或产生不可预料的结果。这在处理可能为 nil 的属性时特别有用。

4.Key路径

KVC处理操作对象的属性之外,还可以操作对象的属性的“复合属性”。
“复合属性”,KVC机制将其称为Key路径。例如,FKOrder对象中包含一个FKItem类型的item属性,而FKItem对象又包含了name属性以及price属性,因此KVC可以通过item.name和item.pricez这种路径来支持操作FKOrder对象的name,price属性。
KVC协议中为操作Key路径的方法如下:

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

通过下面代码来实现:
先定义一个FKOrder的类。

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

@interface FKOrder : NSObject

@property (nonatomic, strong) FKItem* item;
@property (nonatomic, assign) int amount;

@end

NS_ASSUME_NONNULL_END
#import "FKOrder.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        FKOrder* order = [[FKOrder alloc] init];
        //使用KVC方法为amount设置属性值
        [order setValue:@"12" forKey:@"amount"];
        [order setValue:[[FKItem alloc] init] forKey:@"item"];
        //使用setValue:forKeyPath来获取复合值
        [order setValue:@"鼠标" forKeyPath:@"item.name"];
        [order setValue:[NSNumber numberWithInt:20] forKeyPath:@"item.price"];
        NSLog(@"订单包含%@个%@,单价为%@" ,[order valueForKey:@"amount"], [order valueForKeyPath:@"item.name" ],[order valueForKeyPath:@"item.price"]);
        
    }
    return 0;
}

代码运行结果如下:
在这里插入图片描述
实际上,通过 KVC操作对象的性能比通过 setter、getter 方法操作的性能更差,使用KVC编程的优势更加简洁,更适合提炼一些通用性质的代码。由于KVC方式允许通过字符串形式来操作对象的属性,这个字符串既可是常量,也可是变量,因此具有极高的灵活性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值