一、基本概念
KVC(Key-Value Coding)是iOS开发中一种用于间接访问对象属性的机制。通过KVC,你可以使用字符串作为键来访问对象的属性,而不是直接调用属性的访问方法。
最基本的KVC由NSkeyValueCoding协议提供支持,最基本的操作属性的两个方法如下:
setValue:属性值forkey:属性名;为指定属性设置值
valueForKey:属性名:获取指定属性的值
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface User : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * pass;
@end
NS_ASSUME_NONNULL_END
#import <Foundation/Foundation.h>
#import "User.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
User *user = [[AUser alloc] init];
//使用KVC方式为name属性设置值
[user setValue:@"dingzhen" forKey:@"name"];
//使用KVC方式为pass属性设置值
[user setValue:@"123" forKey:@"pass"];
//使用KVC方式获取User对象的属性值
NSLog(@"%@", [aUser valueForKey:@"name"]);
NSLog(@"%@", [aUser valueForKey:@"pass"]);
}
return 0;
}
在KVC编程方式中,无论调用setValue:forKey:方法还是调用valueForKey:方法,都是通过NSString对象来指定被操作属性的,其中forKey标签用于传入属性名。
对于setValue: forKey: 方法,底层的执行机制如下:
1.程序优先考虑调用属性自动合成的setter方法
2.如果该类没有setter方法,KVC机制会搜索该类中名为传入的“_字符串”(例如_name)的成员变量无论该成员变量是在接口或者实现部分定义、无论它用哪个访问控制符修饰,这条KVC底层上是对该成员变量的赋值。
3.如果该类即没有setter方法也没有“_字符串”成员变量,那么KVC机制会搜索该类中名为字符串的成员变量。
4.如果上面三步都没有找到,那么系统会执行该对象的setValue: forUndefinedKey:方法,该方法的实现就是引发一个异常,导致程序结束。
对于valueforKey:代码, 底层的执行机制如下:
1.程序优先考虑调用属性自动合成的getter方法
2…如果该类没有getter方法,KVC机制会搜索该类中名为传入的“_字符串”(例如_name)的成员变量无论该成员变量是在接口或者实现部分定义、无论它用哪个访问控制符修饰,这条KVC底层上是对该成员变量的赋值。
3.如果该类即没有getter方法也没有“_字符串”成员变量,那么KVC机制会搜索该类中名为字符串的成员变量。
4.如果上面三步都没有找到,那么系统会执行该对象的valueforUndefinedKey:方法,该方法的实现就是引发一个异常,导致程序结束。
二、处理不存在的nil
前面提到使用KVC操作属性时,如果这些属性没有setter、getter方法,也不存在对应的成员变量时,KVC将会自动会调用setValue: forUndefinedKey:或valueForUndefinedKey:方法。系统默认该方法的实现是引发一个异常然后结束程序,如果我们有特殊的需求可以重写这两种方法。
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKApple : NSObject
@end
NS_ASSUME_NONNULL_END
#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
int main(int argc, const char * argv[]) {
@autoreleasepool {
FKApple* apple = [[FKApple alloc] init];
[apple setValue:@"大苹果" forKey:@"name"];
[apple valueForKey:@"name"];
}
return 0;
}
三、处理nil值
当调用KVC来设置对象的属性时,如果属性的类型为基本类型并且程序传入了对应类型的值,那么程序可以正常进行,如果属性的类型为字符串类型传入nil值也能正常进行,但是基本类型传入nil值则会自动执行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
四、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的路径获取属性值。
示例代码如下:
#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;
}