概述
KVC 全称 key valued coding 键值编码。
不得不承认KVC在开发过程中是神器一般的存在。如果正确灵活使用kvc,会使得整个开发过程轻松很多。简单而强大。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性.JAVA,C#都有这个机制。ObjC也有,所以你根部不必进行任何操作就可以进行属性的动态读写,就是KVC。
KVC的操作方法由NSKeyValueCoding提供,而他是NSObject的类别,也就是说ObjC中几乎所有的对象都支持KVC操作。它提供一种机制来间接访问对象的属性。直接访问对象是通过调用访问器的方法实现,而KVC不需要调用访问器的设置和获取方法。
KVC的主要方法和用途
- (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值
************************************************************************
当然NSKeyValueCoding类别中还有其他的一些方法,下面列举一些
+ (BOOL)accessInstanceVariablesDirectly;
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供属性值正确性�验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法时面给Value传nil,则会调用这个方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
setValue:forKey:方法:给模型的属性赋值
赋值原理:(以 setIcon为例:)
(1)去模型中查找有没有setIcon方法,就直接调用这个set方法,给模型这个属性赋值[self setIcon:dict[@"icon"]];
(2)如果找不到set方法,接着就会去寻找有没有icon属性,如果有,就直接访问模型中icon = dict[@"icon"];
(3)如果找不到icon属性,接着又会去寻找_icon属性,如果有,直接_icon = dict[@"icon"];
(4)如果都找不到就会报错
[<Flag 0x7fb74bc7a2c0> setValue:forUndefinedKey:]
- 直接赋值
- 支持键值路径
- 支持操作符
- 字典转模型
- 修改UI私有属性
直接赋值
对于属性值我们可以通过setter 和getter方法,或读取或写入数值。
当然我们也可以用KVC 的方式进行读写数据。
举个例子:
@interface Person : NSObject
@property(nonatomic,copy,readonly)NSString* name;
@property(nonatomic,assign)NSNumber *age;
@end
Person *person=[[Person alloc] init];
[person setValue:@"25" forKey:@"age"];
[person setValue:@"皮拉夫大王" forKey:@"name"];
NSLog(@"person 的名字是%@",person.name);
NSLog(@"person 的年领是%@",[person valueForKey:@"age"]);
从上面的例子中我们可以发现:
- 只读的属性怎么可以赋值?
- 还有age属性明明是NSNumber类型的,怎么可以把字符串赋给它?
(1)KVC 不但能够赋值,而且还能破坏只读的特性。
(2)更重要的是KVC 有自动装箱(自动类型转换)的功能,我们不需要去转换类型了。由于开发过程中数据领域是字符串的天下,所以这个自动装箱的功能的确是极好的。
(3)KVC可以访问成员变量,无论是否提供getter/setter方法,无论可见性是怎样,是否有readonly修饰。
支持键值路径
什么叫支持键值路径?说白了就是支持多层级属性直接赋值。假如现在有一个书籍类,类中包含了书籍的名称name。书籍可以被Person所拥有(就是可以作为person的属性)
#import <Foundation/Foundation.h>
@interface Book : NSObject
@property(nonatomic,copy)NSString* name;
@end
那么我们就可以这样来用
Person *person=[[Person alloc] init];
Book *myBook=[[Book alloc] init];
person.book=myBook;
[person setValue:@"程序员摊煎饼指南" forKeyPath:@"book.name"];
NSLog(@"%@",[person valueForKeyPath:@"book.name"]);
需要说明的是:在不必要的情况下使用keyPath会浪费性能。
支持操作符
格式为:[p valueForKeyPath:@"Left keypath部分.@Collectionoperator部分.Right keypath部分”];
Left keypath部分:需要操作对象路径。
Collectionoperator部分:通过@符号确定使用的集合操作。
Right keypath部分:需要进行集合操作的属性。
(1)简单集合操作符
@count: 返回一个值为集合中对象总数的NSNumber对象。
@sum: 首先把集合中的每个对象都转换为double类型,然后计算其总,最后返回一个值为这个总和的NSNumber对象。
@avg: 把集合中的每个对象都转换为double类型,返回一个值为平均值的NSNumber对象。
@max: 使用compare:方法来确定最大值。所以为了让其正常工作,集合中所有的对象都必须支持和另一个对象的比较。
@min: 和@max一样,但是返回的是集合中的最小值。
[products valueForKeyPath:@"@count"];
[products valueForKeyPath:@"@sum.price"];
[products valueForKeyPath:@"@avg.price"];
[products valueForKeyPath:@"@max.price"];
[products valueForKeyPath:@"@min.launchedOn"];
如果操作对象(集合/数组)内是NSNumber,可以这样写
[products valueForKeyPath:@"@sum.self"];
1.使用类的方法做操作符
NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [array valueForKeyPath:@"uppercaseString"]);
输出:
( NAME, W, AA, JIMSA)
相当于数组中的每个成员执行了uppercaseString方法,然后把返回的对象组成一个新数组返回。既然可以用uppercaseString方法,那么NSString的其他方法也是可以的.
2.剔除重复数据
NSArray *array = @[@"name", @"w", @"aa", @"jimsa", @"aa"]; NSLog(@"%@",
[array valueForKeyPath:@"@distinctUnionOfObjects.self"]);
打印:
( name, w, jimsa, aa )
3.对NSDictionary数组快速找出相应key对的值
NSArray *array = @[
@{@"name1" : @"cookeee",@"code" : @1},
@{@"name": @"jim",@"code" : @2},
@{@"name": @"jim",@"code" : @1},
@{@"name": @"jbos",@"code" : @1}];
NSLog(@"%@", [array valueForKeyPath:@"name"]);
直接得到字典中name key对应的值组成的数组,显然比循环取值再加入到新数组中方便快捷,
由于第一个元素没有name这个Key ,所以里面为<null>)
( "<null>", jim, jim, jbos )
(2)对象操作符
@unionOfObjects:返回操作对象内部的所有对象,返回值为数组
@distinctUnionOfObjects:返回操作对象内部的不同对象,返回值为数组
(3)数组和集合操作符
@unionOfArrays:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的所有对象,返回值为数组
@distinctUnionOfArrays:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的不同对象,返回值为数组
@distinctUnionOfSets:返回操作对象(且操作对象内对象必须是数组/集合)中数组/集合的所有对象,返回值为集合
提示:集合无重复元素
(4)自定义操作符
以NSArray
为例,runtime跑一下
#import <objc/runtime.h>
unsigned int outconunt = 0;
Method *meths =class_copyMethodList([NSArray class], &outconunt);
for (int i = 0; i<outconunt; i++) {
Method meth = meths[i];
SEL metSel = method_getName(meth);
NSLog(@"L : %@",NSStringFromSelector(metSel));
}
可以看到一大堆的方法,由于太多了,无法截图完整的,看上图红框中的代码是不是很眼熟。
猜想:实现_<key>ForKeyPath:
即可自定义Collection Operators
尝试定义一个名为@jack
的Collection Operators
可见,只要写好实现,完全可以自定义一些比较有用的Collection Operators
字典转模型
下面是常见的使用方法,目前有很多KVC 和 Runtime一起使用达到Json数据自动转模型的方法,本文暂时不做介绍。
@implementation Model
-(instancetype)initWithDict:(NSDictionary *)dict
{
if (self=[super init])
{
// 字典转模型的常用语句
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
if ([key isEqualToString:@"id"])
{
self.whoCare=value;
}
}
@end
修改UI私有属性
(1)如何实现这样的效果?
系统默认的是这样的:
看了系统自带的API,无法解决这个问题,现在有两个路:
- 自定义PageControl
- 通过runtime遍历出UIPageControl所有属性(包括私有成员属性)利用KVC可强制修改系统的PageControl,达到想要的效果。充满了黑科技之感
u_int count;
Ivar *properties =class_copyIvarList([UIPageControl class], &count);
for (int i = 0; i<count; i++)
{
const char* propertyName =ivar_getName(properties[i]);
const char* propertyType = ivar_getTypeEncoding(properties[i]);
NSLog(@"属性:%@ = %@",[NSString stringWithUTF8String: propertyName],[NSString stringWithUTF8String: propertyType]);
}
结果非常满意,果然找到我想要的图片设置属性。
然后通过KVC设置自定义图片,实现了效果,代码如下:
UIPageControl *pageControl = [[UIPageControl alloc] init];
[pageControl setValue:[UIImage imageNamed:@"home_slipt_nor"] forKeyPath:@"_pageImage"];
[pageControl setValue:[UIImage imageNamed:@"home_slipt_pre"] forKeyPath:@"_currentPageImage"];
(2)还有很多其他的修改UI控件私有属性的常见操作:
比如修改UISearchBar的输入框显示效果。
UITextField * searchField = [searchBar valueForKey:@"searchField"];
[searchField setValue:GrayTextColor forKeyPath:@"placeholderLabel.textColor"];
[searchField setValue:[UIFont boldSystemFontOfSize:10] forKeyPath:@"_placeholderLabel.font"];
UITextField *searchField = [[mySearchBar subviews] lastObject];
[searchField setReturnKeyType:UIReturnKeyDone];
参考文章:
iOS开发技巧系列---详解KVC
KVC进阶(三)
iOS底层-KVC使用实践以及实现原理
iOS开发技巧系列---详解KVC(我告诉你KVC的一切)
网友评论