前言
一、概述
KVC全称是Key Value Coding(键值编码),是可以通过对象属性名称(Key)直接对属性值(value)编码(coding)“编码”可以理解为“赋值及访问”。而不需要调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性,而不是在编译时确定。
KVC的优势是在没有访问器(setter、getter)方法的类中,此时点语法无法使用。
KVC提供了一种间接访问其属性方法或成员变量的机制,可以通过字符串来访问对应的属性方法或成员变量。
二、使用
KVC的定义都是对NSObject的扩展来实现的,Objective-c中有个显式的NSKeyValueCoding类别名,所以对于所有继承了NSObject在类型,都能使用KVC。
在 Swift 中处理 KVC和 Objective-C 中还是有些细微的差别。比如,Objective-C 中所有的类都继承自 NSObject,而 Swift 中却不是,所以我们在 Swift 中需要显式的声明继承自 NSObject。
因为 Swift 中的 Optional 机制,所以 valueForKey 方法返回的是一个 Optional 值,我们还需要对返回值做一次解包处理,才能得到实际的属性值。
1、基本使用
下面是KVC中最常用的几个方法:
1 2 3 4 5 6 7 8 9 10 11
| //直接通过Key来取值 - (nullable id)valueForKey:(NSString *)key;
//通过Key来设值 - (void)setValue:(nullable id)value forKey:(NSString *)key;
//通过KeyPath来取值 - (nullable id)valueForKeyPath:(NSString *)keyPath;
//通过KeyPath来设值 - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
|
举例:Book类拥有书名name属性以及author,且author类拥有一个address属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @interface Author : NSObject @property(strong, nonatomic) NSString* address; @end
@implementation Author @end
@interface Book : NSObject @property(strong, nonatomic) NSString* name;// 书名 @property(strong, nonatomic) Author* author;// 作者 @end
@implementation Book @end
|
使用时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| // ViewController.m #import "ViewController.h" #import "Book.h"
@interface ViewController () @end @implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; Book *book = [[Book alloc]init]; [book setValue:@"Hello world" forKey:@"name"]; // 设置值 NSLog(@"age=%@",[book valueForKey:@"name"]); // 取值 // keyPath 方式 [book setValue:@"ShangHai" forKeyPath:@"author.address"]; // 设置值 NSLog(@"author.address=%@",[book valueForKeyPath:@"author.address"]);//取值 }
@end
|
2、底层执行机制
- [object setValue:@”value” forKey:@”property”]
存取器(setter方法)匹配:先寻找与setKey同名的方法。找到直接赋值。[self setProperty:@”value”]。
实例变量匹配:寻找与key,_isKey,_key,isKey同名的实例变量,直接赋值。property = value或_property = value等。
找不到,就会直接报错 setValue:forUndefinedKey:报找不到的错误。
如果我们想让这个类禁用KVC,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUNdefinedKey:方法。
1 2
| + (BOOL)accessInstanceVariablesDirectly; //默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
|
- [object valueForKey:@”property”]
访问器(getter方法)匹配:先寻找与key,isKey, getKey (实测还有_key)同名的方法。
实例变量匹配:寻找与key, _key,isKey,_isKey同名的实例变量。
如果还没有找到的话,调用valueForUndefinedKey:。
若value值为BOOL或Int等值类型时,KVC可以自动的将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。
1 2 3 4 5 6 7 8 9 10 11 12
| Ps:swift中使用keyPath的方式,但是仅支持struct
struct Book { var name:String }
var book = Book(name: "Swift") // set book[keyPath: name] = "swift4" // get let valueOfName = book[keyPath:name]
|
3、键值验证
在实际开发中我们获取对一些Key的value值有一些特殊的要求。KVC为我们提供了验证Key对应的Value是否可用的方法:
1
| - (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
|
这为我们提供了一次纠错的机会。但是,KVC是不会自动调用键值验证方法的,就是说我们如果想要键值验证则需要手动验证。也就是说,需要自己需要验证的类中重写-(BOOL)-validate:error:,默认返回Yes。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @implementation Book - (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ NSString* name = *value; name = name.capitalizedString; if ([name isEqualToString:@"Not-name"]) { return NO; } return YES; } @end - (void)viewDidLoad { [super viewDidLoad]; Book *book = [[Book alloc]init]; NSError* error; NSString *value = @"Not-name"; // result 为 NO //NSString *value = @"BookName"; // result 为 YES BOOL result = [book validateValue:&value forKey:@"name" error:&error]; if (result) { NSLog(@"OK"); } else { NSLog(@"NO"); } }
|
4、不存在的key及nil值处理
我们可以考虑重写setValue: forUndefinedKey:方法与valueForUndefinedKey:方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"您设置的key:[%@]不存在", key); NSLog(@"您设置的value为:[%@]", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"您访问的key:[%@]不存在", key); return nil;
}
|
当程序尝试为某个属性设置nil值时,如果该属性并不接受nil值,那么程序将会自动执行该对象的setNilValueForKey:方法。我们同样可以重写这个方法:
1 2 3 4 5 6 7 8 9
| - (void)setNilValueForKey:(NSString *)key { //对不能接受nil的属性进行处理 if ([key isEqualToString:@"price"]) { //对应你具体的业务来处理 price = 0; }else { [super setNilValueForKey:key]; } }
|
5、一些函数操作
KVC同时还提供了一些较复杂的函数,主要有下面这些:
目前总共有五个函数:@avg, @count , @max , @min ,@sum5
共有两个:@distinctUnionOfObjects 及 @unionOfObjects
它们的返回值都是NSArray。两者的区别在于:前者会去除返回值中重复的数据,后者则将数据全部返回。
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| @interface Book : NSObject @property(assign, nonatomic) NSInteger price; @end
@implementation Book @end
@implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Book *book1 = [Book new]; book1.price = 40; Book *book2 = [Book new]; book2.price = 20; Book *book3 = [Book new]; book3.price = 30; Book *book4 = [Book new]; book4.price = 10; // 价格重复的 Book *book5 = [Book new]; book5.price = 10; NSLog(@"----------集合运算符----------"); NSArray* arr = @[book1,book2,book3,book4]; NSNumber* sum = [arr valueForKeyPath:@"@sum.price"]; NSLog(@"sum:%f",sum.floatValue); NSNumber* avg = [arr valueForKeyPath:@"@avg.price"]; NSLog(@"avg:%f",avg.floatValue); NSNumber* count = [arr valueForKeyPath:@"@count"]; NSLog(@"count:%f",count.floatValue); NSNumber* min = [arr valueForKeyPath:@"@min.price"]; NSLog(@"min:%f",min.floatValue); NSNumber* max = [arr valueForKeyPath:@"@max.price"]; NSLog(@"max:%f",max.floatValue); NSLog(@"----------对象操作符----------"); NSArray* arrBooks = @[book1,book2,book3,book4, book5]; NSLog(@"distinctUnionOfObjects"); NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"]; for (NSNumber *price in arrDistinct) { NSLog(@"%f",price.floatValue); } NSLog(@"unionOfObjects"); NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"]; for (NSNumber *price in arrUnion) { NSLog(@"%f",price.floatValue); } } @end
---输出结果:--- ----------集合运算符---------- sum:100.000000 avg:25.000000 count:4.000000 min:10.000000 max:40.000000
----------对象操作符---------- distinctUnionOfObjects 10.000000 20.000000 30.000000 40.000000 unionOfObjects 40.000000 20.000000 30.000000 10.000000 10.000000
|
三、实际应用
1、访问私有变量
对于类里的私有属性,Objective-C是无法直接访问的,但是KVC是可以的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @interface Book : NSObject { NSString * owner; } @end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad];
Book *book = [Book new]; // 这种访问方式,会直接报错。因为owner是私有属性。 // book.owner = @"tao"; // 利用KVC访问到私有变量 [book setValue:@"tao" forKey:@"owner"]; NSLog([book valueForKey:@"owner"]); // 输出 tao } @end
|
2、修改一些控件的内部属性
在开发中,我们常常需要对一些控件的某些属性做修改,但是很多UI控件都由很多内部UI控件组合而成的,系统并没有提供这访问这些控件的API,这样我们就无法正常地访问和修改这些控件的样式。但是,KVC可以帮我们解决大部分这种类型的问题。
3、结合Runtime打造字典转model
可以利用KVC和运行时将字典转换为模型。
具体代码可以我的Github上查看。
四、总结
上面的几点就是本人对KVC学习的一些记录,如有不妥之处,请大家多多指正。关于KVC更多更详细的资料,大家可以去到官方文档Key-Value Coding Programming Guide查看学习。