iOS -- KVC底层剖析及应用场景
2018-04-19 本文已影响53人
20b347b28fc9
KVC底层剖析及应用场景
1.方法介绍
// @interface NSObject(NSKeyValueCoding)
// 以下方法都是NSKeyValueCoding.h中的方法
// 赋值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// 取值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (nullable id)valueForUndefinedKey:(NSString *)key;
// 字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
// 模型转字典
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
2.调用顺序--读取部分
下面做了简单的调用顺序验证,代码没技术含量,看看就行
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
// 创建类方法--目的为了在方法中初始化
+ (instancetype)person;
@end
#import "Person.h"
@implementation Person
+ (instancetype)person{
Person *person = [[Person alloc] init];
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
return person;
}
- (NSString *)name{
return @"func name";
}
- (NSString *)getName{
return @"func getName";
}
// 这个方法决定是否可以通过KVC获取私有变量--默认是YES
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
}
@end
Person *p = [Person person];
// 此处调用setValueForKey 来取值
NSString *str = [p valueForKey:@"name"];
通过setValueForKey调用具体是怎样的顺序呢?
- 总的原则:先找方法,再找变量
- 1.先找方法,依次寻找下面方法: -getName -name
- 2.如果上面两个方法没有找到,调用+accessInstanceVariablesDirectly
* 如果返回NO,表示不访问变量,直接调用 -valueForUndefinedKey
* 如果返回YES,继续下面步骤访问相关变量
- 3. 访问变量,依次虚招下面变量: _name _isName name isName
- 4. 如果没找到就调用-valueForUndefinedKey 【runtime为防止这里崩溃,可以通过其他方法补救,具体下面解释】
3.调用顺序--赋值部分
赋值部分跟取值部分相似,具体区别就在方法名上,区别如下:
方法名: -setName -setIsName
4.容器类KVC的情况
容器类有点区别,除了属性外,会调用下面这两个方法,进行成员的设置
@interface Person : NSObject
{
NSMutableArray *nameArray;
}
// 创建类方法--目的为了在方法中初始化
+ (instancetype)person;
@end
#import "Person.h"
@implementation Person
// 容器类 方法
- (NSInteger)countOfNameArray{
return 2;
}
-(id)objectInNameArrayAtIndex:(NSInteger)index{
if (index == 0) {
return @"1";
}
return @"2";
}
@end
// 外部调用--触发
Person *p = [Person person];
NSArray *array = [p valueForKey:@"nameArray"];
5.基本数据类型KVC的情况
@interface Person : NSObject
{
int age;
}
// 外部调用--触发
Person *p = [Person person];
id age = [p valueForKey:@"age"];
// 这里真是类型是NSNumber
注意:属性中age是基本数据类型,但是通过KVC获取到值之后,转换成为NSNumber类型
6.KVC使用场景
6.1 属性赋值
- 1. -setValue: forKey
- 2. -setValue: forKeyPath
这两个方法区别不做赘述,比较初级
6.2 修改系统私有变量
这里简答做举例说明,也不做相信代码展示
1. UITextField的placeholder的颜色啊
2. UIPageControl的底部显示图片,代码如下
[pageControl setValue:image1 forKeyPath:@"pageImage"];
[pageControl setValue:image2 forKeyPath:@"currentPageImage"];
这里做一下runtime补充--获取私有变量的方法
// 导入头文件
#import <objc/runtime.h>
- (void)getIvarList{
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([UIPageControl class], &count);
for (unsigned int i = 0; i < count; i ++)
{
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSLog(@"%s ", name);
}
// 内存管理--释放
free(ivars);
}
6.3 字典转模型
字典转模型一般配合下面几个方法
+ (instancetype)personModelWithDict:(NSDictionary *)dict{
Person *person = [[Person alloc] init];
[person setValuesForKeysWithDictionary:dict];
return person;
}
// 处理特殊的key和value
- (void)setValue:(id)value forKey:(NSString *)key{
if ([key isEqualToString:@"id"]) {
[super setValue:value forKey:@"userId"];
}
}
// 容错处理
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
}
6.4 字典转模型
NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"age"]];
6.5 访问容器类(如数组)中元素的属性值
Person *p1 = [Person person];
p1.name = @"name-1";
Person *p2 = [Person person];
p2.name = @"name-2";
Person *p3 = [Person person];
p3.name = @"name-3";
NSArray *array = @[p1, p2, p3];
NSArray *nameArray = [array valueForKey:@"name"];
NSLog(@"%@", nameArray);
6.6 防止私有变量被外界通过KVC修改
我们看到了KVC可以修改私有变量,但如何防止通过KVC被修改这些变量呢?这就需要用到我们前面介绍过的一个方法 + (BOOL)accessInstanceVariablesDirectly;
如果防止被修改,记得返回NO;