1. 基本介绍
KVC
全称Key-Value Coding
(键值编码),是一种由NSKeyValueCoding
非正式协议启用的机制,对象采用它来提供对其属性的间接访问当一个对象符合键值编码时,它的属性可以通过字符串
Key
来寻址。这种间接访问机制补充了实例变量及其关联的访问方法所提供的直接访问
2. API介绍
KVC
的API
在Foundation
框架中,暂未开源KVC
本质上是对NSObject
、NSArray
、NSDictionary
、NSMutableDictionary
、NSOrderedSet
、NSSet
等对象,实现NSKeyValueCoding
分类,赋予它们Key-Value Coding
的能力
通过Key
读取和存储
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
通过keyPath
读取和存储
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
其他API
//默认返回YES,表示如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员
+ (BOOL)accessInstanceVariablesDirectly;
//KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确,为不正确的值做一个替换值或者拒绝设置新值并返回错误原因
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//如果Key不存在,且KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常
- (nullable id)valueForUndefinedKey:(NSString *)key;
//和上一个方法一样,但这个方法是设值
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//如果你在SetValue方法时给Value传nil,则会调用这个方法
- (void)setNilValueForKey:(NSString *)key;
//输入一组Key,返回该组Key对应的Value,再转成字典返回,用于将Model转到字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
3. API使用
3.1 对象属性
一个对象通常在其接口声明中指定属性,这些属性属于以下几个类别之一:
属性:这些是简单的值,例如标量、字符串或布尔值。值对象
NSNumber
和其他不可变类型NSColor
也被视为属性一对一的关系:这些是具有自己属性的可变对象。一个对象的属性可以在不改变对象本身的情况下改变
- 例如:一个银行账户对象可能有一个
owner
属性,它是一个Person
对象的实例,它本身有一个address
属性。所有者的地址可能会更改,而不会更改银行帐户持有的所有者参考。银行账户的所有者没有改变。只有他们的地址
- 例如:一个银行账户对象可能有一个
对多关系:这些是集合对象。通常使用
NSArray
或的实例NSSet
来保存此类集合,也可能是自定义集合类型
3.1.1 使用Key
寻址
LGPerson *person = [[LGPerson alloc] init];
// 一般setter方法
person.name = @"LG_Cooci";
person.age = 18;
person->myName = @"cooci";
// 非正式协议 - 间接访问
[person setValue:@"KC" forKey:@"name"];
3.1.2 使用路径寻址
LGStudent *student = [LGStudent alloc];
student.subject = @"大师班";
person.student = student;
[person setValue:@"Swift" forKeyPath:@"student.subject"];
NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
-------------------------
Swift
3.2 集合属性
该协议为集合对象访问定义了三种不同的代理方法,每种方法都有一个键和一个键路径变体:
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
- 它们返回一个行为类似于
NSMutableArray
对象的代理对象
- 它们返回一个行为类似于
mutableSetValueForKey:
和mutableSetValueForKeyPath:
- 它们返回一个行为类似于
NSMutableSet
对象的代理对象
- 它们返回一个行为类似于
mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
- 它们返回一个行为类似于
NSMutableOrderedSet
对象的代理对象
- 它们返回一个行为类似于
3.2.1 修改数组元素
person.array = @[@"1",@"2",@"3"];
// 第一种:创建新的数组,KVC赋值就
NSArray *array = [person valueForKey:@"array"];
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
// 第二种:使用mutableArrayValueForKey:
NSMutableArray *mArray = [person mutableArrayValueForKey:@"array"];
mArray[0] = @"200";
3.2.2 数组取值
LGStudent *p = [LGStudent new];
p.penArr = [NSMutableArray arrayWithObjects:@"pen0", @"pen1", @"pen2", @"pen3", nil];
NSArray *arr = [p valueForKey:@"penArr"];
NSLog(@"pens = %@", arr);
NSEnumerator *enumerator = [arr objectEnumerator];
NSString* str = nil;
while (str = [enumerator nextObject]) {
NSLog(@"%@", str);
}
-------------------------
pens = (
pen0,
pen1,
pen2,
pen3
)
遍历:pen0
遍历:pen1
遍历:pen2
遍历:pen3
3.2.3 模型字典转换
NSDictionary* dict = @{
@"name":@"Cooci",
@"nick":@"KC",
@"subject":@"iOS",
@"age":@18,
@"length":@180
};
LGStudent *s = [[LGStudent alloc] init];
[s setValuesForKeysWithDictionary:dict];
NSLog(@"模型:%@",s);
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [s dictionaryWithValuesForKeys:array];
NSLog(@"字典:%@",dic);
-------------------------
模型:<LGStudent: 0x2835b8990>
字典:{
age = 18;
name = Cooci;
}
3.2.4 消息传递
NSArray *array = @[@"Hank",@"Cooci",@"Kody",@"CC"];
NSArray *lenStr= [array valueForKeyPath:@"length"];
NSLog(@"长度:%@",lenStr);
NSArray *lowStr= [array valueForKeyPath:@"lowercaseString"];
NSLog(@"小写:%@",lowStr);
-------------------------
长度:(
4,
5,
4,
2
)
小写:(
hank,
cooci,
kody,
cc
)
3.3 集合运算符
集合操作符表现出三种基本的行为类型:
聚合操作符:以某种方式合并集合的对象,并返回一个通常与在正确键路径中命名的属性的数据类型匹配的对象。
@count
操作符是个例外,它不接受正确的键路径,并且总是返回一个NSNumber
实例数组操作符:返回一个
NSArray
实例,该实例包含命名集合中持有的对象的一些子集嵌套操作符:处理包含其他集合的集合,并根据操作符返回一个
NSArray
或NSSet
实例,以某种方式组合嵌套集合的对象
3.3.1 聚合操作符
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *p = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"lengthArray:%@", [personArray valueForKey:@"length"]);
float avg = [[personArray valueForKeyPath:@"@avg.length"] floatValue];
NSLog(@"@avg:%f", avg);
int count = [[personArray valueForKeyPath:@"@count.length"] intValue];
NSLog(@"@count:%d", count);
int sum = [[personArray valueForKeyPath:@"@sum.length"] intValue];
NSLog(@"@sum:%d", sum);
int max = [[personArray valueForKeyPath:@"@max.length"] intValue];
NSLog(@"@max:%d", max);
int min = [[personArray valueForKeyPath:@"@min.length"] intValue];
NSLog(@"@min:%d", min);
-------------------------
lengthArray:(
177,
179,
175,
181,
175,
175
)
@avg:177.000000
@count:6
@sum:1062
@max:181
@min:175
3.3.2 数组操作符
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *p = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"lengthArray:%@", [personArray valueForKey:@"length"]);
NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.length"];
NSLog(@"不排重:%@", arr1);
NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
NSLog(@"排重:%@", arr2);
-------------------------
lengthArray:(
177,
183,
177,
175,
181,
183
)
不排重:(
177,
183,
177,
175,
181,
183
)
排重:(
175,
181,
177,
183
)
3.3.3 嵌套操作符
NSMutableArray *personArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGStudent *student = [LGStudent new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[student setValuesForKeysWithDictionary:dict];
[personArray1 addObject:student];
}
NSMutableArray *personArray2 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
LGPerson *person = [LGPerson new];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[person setValuesForKeysWithDictionary:dict];
[personArray2 addObject:person];
}
// 嵌套数组
NSArray* nestArr = @[personArray1, personArray2];
NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.length"];
NSLog(@"排重:%@", arr);
NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.length"];
NSLog(@"不排重:%@", arr1);
-------------------------
排重:(
185,
183,
179,
177
)
不排重:(
177,
177,
177,
183,
185,
177,
183,
179,
179,
179,
179,
179
)
3.4 访问非对象属性
默认键值编码实现使用
NSNumber
实例包装的标量类型默认存取用于包装和展开常见
NSPoint
、NSRange
、NSRect
、和NSSize
结构结构类型,可以包装在一个
NSValue
对象中
3.4.1 包装在NSNumber
对象中的标量类型
数据类型 | 创建方法 | 存取方法 |
---|---|---|
BOOL | numberWithBool: | boolValue (在 iOS 中) charValue (在 macOS 中)* |
char | numberWithChar: | charValue |
double | numberWithDouble: | doubleValue |
float | numberWithFloat: | floatValue |
int | numberWithInt: | intValue |
long | numberWithLong: | longValue |
long long | numberWithLongLong: | longLongValue |
short | numberWithShort: | shortValue |
unsigned char | numberWithUnsignedChar: | unsignedChar |
unsigned int | numberWithUnsignedInt: | unsignedInt |
unsigned long | numberWithUnsignedLong: | unsignedLong |
unsigned long long | numberWithUnsignedLongLong: | unsignedLongLong |
unsigned short | numberWithUnsignedShort: | unsignedShort |
3.4.2 包装和展开常见结构
数据类型 | 创建方法 | 存取方法 |
---|---|---|
NSPoint | valueWithPoint: | pointValue |
NSRange | valueWithRange: | rangeValue |
NSRect | valueWithRect: (仅限 macOS) | rectValue |
NSSize | valueWithSize: | sizeValue |
3.4.3 自定义结构体
typedef struct {
float x, y, z;
} ThreeFloats;
ThreeFloats floats = {1.,2.,3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *value1 = [person valueForKey:@"threeFloats"];
NSLog(@"%@",value1);
ThreeFloats th;
[value1 getValue:&th];
NSLog(@"%f-%f-%f",th.x,th.y,th.z);
-------------------------
{length = 12, bytes = 0x0000803f0000004000004040}
1.000000-2.000000-3.000000
3.5 验证属性
由于特定于属性的验证方法通过引用接收值和错误参数,因此验证具有三种可能的结果:
验证方法认为值对象有效并在
YES
不改变值或错误的情况下返回验证方法认为值对象无效,但选择不更改它。在这种情况下,该方法返回
NO
并将错误引用(如果由调用者提供)设置为NSError
指示失败原因的对象验证方法认为值对象无效,但会创建一个新的有效对象作为替换。在这种情况下,该方法返回
YES
同时保持错误对象不变。在返回之前,该方法修改值引用以指向新的值对象。当它进行修改时,该方法总是创建一个新对象,而不是修改旧对象,即使值对象是可变的
name
属性的验证
LGPerson *per = [LGPerson new];
NSError* error;
NSString* name = @"John";
if (![per validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
4. KVC的存储过程
查找setter
方法
- (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
// 没有调用
- (void)_setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
查找实例变量
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation LGPerson
#pragma mark - 关闭/开启实例变量
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
@end
调用setValue:forKey:
的流程:
【第一步】查找是否存在以下三种
setter
方法,查找顺序:set<Key>:
→_set<Key>
→setIs<Key>
key
是指成员变量名,首字母大小写需要符合KVC
的命名规范存在任意一种
setter
方法,直接设置属性的value
如果都不存在,进入【第二步】
【第二步】:查找
accessInstanceVariablesDirectly
方法的返回值返回
YES
,查找间接访问的实例变量进行赋值,查找顺序:_<key>
→_is<Key>
→<key>
→is<Key>
如果找到其中任意一个实例变量,可对其赋值
如果都未找到,进入【第三步】
返回
NO
,进入【第三步】
【第三步】如果
setter
方法或实例变量都没有找到,系统会调用该对象的setValue:forUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常
5. KVC的读取过程
查找getter
方法
- (NSString *)getName{
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
查找集合类型
// 个数
- (NSUInteger)countOfPens{
NSLog(@"%s",__func__);
return [self.arr count];
}
// 获取值
- (id)objectInPensAtIndex:(NSUInteger)index {
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"pens %lu", index];
}
查找NSSet
类型
// 个数
- (NSUInteger)countOfBooks{
NSLog(@"%s",__func__);
return [self.set count];
}
// 是否包含这个成员对象
- (id)memberOfBooks:(id)object {
NSLog(@"%s",__func__);
return [self.set containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfBooks {
// objectEnumerator
NSLog(@"来了 迭代编译");
return [self.arr reverseObjectEnumerator];
}
查找实例变量
#import <Foundation/Foundation.h>
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation LGPerson
#pragma mark - 关闭/开启实例变量
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
@end
调用valueForKey:
的流程:
【第一步】查找
getter
方法,查找顺序:get<Key>
→<key>
→is<Key>
→_<key>
如果找到,进入【第五步】
如果未找到,进入【第二步】
【第二步】查找
countOf<Key>
、objectIn<Key>AtIndex:
、<key>AtIndexes:
如果找到第一个或其他两个中的至少一个,则会创建一个响应所有
NSArray
方法的集合代理对象,并返回该对象,即:NSKeyValueArray
,属于NSArray
的子类。代理对象随后将接收到任何NSArray
消息转换为countOf<Key>
、objectIn<Key>AtIndex:
、<key>AtIndexes:
消息的某种组合,用来创建键值编码对象如果原始对象还实现了一个名为
get<Key>:range:
之类的可选方法,则代理对象也将在适当时使用该方法方法名的命名规则,要符合
KVC
的标准命名方法,包括方法签名
如果未找到,进入【第三步】
【第三步】查找以下几种方法,
countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
如果三个方法都找到,则会创建一个响应所有
NSSet
方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet
消息转换为countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
消息的某种组合,用于创建它的对象如果未找到,进入【第四步】
【第四步】查找
accessInstanceVariablesDirectly
方法的返回值返回
YES
,依次搜索_<key>
、_is<Key>
、<key>
或is<Key>
的实例变量找到实例变量,直接获取实例变量的值,进入【第五步】
未找到,进入【第六步】
返回
NO
,进入【第六步】
【第五步】根据搜索到的属性值的类型,返回不同的结果
如果是对象指针,则直接返回结果
如果是
NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回它如果是
NSNumber
不支持的标量类型,转换为NSValue
对象并返回该对象
【第六步】如果以上所有方法都失败,系统调用该对象的
valueForUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常
6、自定义KVC
6.1 相关方法
- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}
6.2 KVC
存储
// 自定义KVC-存储
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// 1: 判断key
if (key == nil || key.length == 0) {
return;
}
// 2: setter:set<Key>:→_set<Key>→setIs<Key>
// key要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判断能否直接赋值实例变量,如果accessInstanceVariablesDirectly返回NO,奔溃
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 间接变量
// 获取 ivar -> 遍历 containsObjct
// 4.1: 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key>→_is<Key>→<key>→is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2: 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3: 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5: 找不到,奔溃
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
6.3 KVC
读取
- (nullable id)lg_valueForKey:(NSString *)key{
// 1: 判断key
if (key == nil || key.length == 0) {
return nil;
}
// 2: 找到相关方法get<Key>→<key> countOf<Key> objectIn<Key>AtIndex
// key要大写
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}
else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}
else if ([self respondsToSelector:NSSelectorFromString(isKey)]){
return [self performSelector:NSSelectorFromString(isKey)];
}
else if ([self respondsToSelector:NSSelectorFromString(_key)]){
return [self performSelector:NSSelectorFromString(_key)];
}
else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3: 判断是否能够直接赋值实例变量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 找相关实例变量进行赋值
// 4.1: 定义一个收集实例变量的可变数组
NSMutableArray *mArray = [self getIvarListName];
// _<key>→_is<Key>→<key>→is<Key>
// _name→_isName→name→isName
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
7. 使用场景
7.1 动态设值和取值
通过
Key
读取和存储:setValue:forKey:
和valueForKey:
通过
keyPath
读取和存储:setValue:forKeyPath:
和valueForKeyPath:
7.2 通过KVC
访问和修改私有变量
在日常开发中,对于类的私有属性,在外部定义的对象,是无法直接访问私有属性的。但是对于KVC
而言,一个对象没有自己的隐私,所以可以通过KVC
修改和访问任何私有属性
7.3 多值操作
模型字典转换
//字典转模型
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
//模型转字典
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
7.4 修改一些系统空间的内部属性
在日常开发中,很多UI
控件都是在其内部由多控件组合而成,这些内部控件苹果并没有提供访问的API
,但是使用KVC
可以解决这个问题,例如:自定义tabbar
、修改UITextField
中的placeHolderText
7.5 用KVC
实现高阶消息传递
在对容器类使用KVC
时,valueForKey:
将会被传递给容器中的每一个对象,而不是对容器本身进行操作,结果会被添加到返回的容器中,这样,可以很方便的操作集合,然后返回另一个集合
总结
基本介绍:
KVC
全称Key-Value Coding
(键值编码),是一种由NSKeyValueCoding
非正式协议启用的机制,对象采用它来提供对其属性的间接访问- 当一个对象符合键值编码时,它的属性可以通过字符串
Key
来寻址。这种间接访问机制补充了实例变量及其关联的访问方法所提供的直接访问 - 官方文档:Key-Value Coding Programming Guide
API
介绍:
KVC
的API
在Foundation
框架中,暂未开源KVC
本质上是对NSObject
、NSArray
、NSDictionary
、NSMutableDictionary
、NSOrderedSet
、NSSet
等对象,实现NSKeyValueCoding
分类,赋予它们Key-Value Coding
的能力- 通过
Key
读取和存储- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- 通过
keyPath
读取和存储- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
API
使用:
- 对象属性
- 属性:这些是简单的值,例如标量、字符串或布尔值。值对象
NSNumber
和其他不可变类型NSColor
也被视为属性 - 一对一的关系:这些是具有自己属性的可变对象。一个对象的属性可以在不改变对象本身的情况下改变
- 对多关系:这些是集合对象。通常使用
NSArray
或的实例NSSet
来保存此类集合,也可能是自定义集合类型
- 属性:这些是简单的值,例如标量、字符串或布尔值。值对象
- 集合属性
mutableArrayValueForKey:
和mutableArrayValueForKeyPath:
- 它们返回一个行为类似于
NSMutableArray
对象的代理对象
- 它们返回一个行为类似于
mutableSetValueForKey:
和mutableSetValueForKeyPath:
- 它们返回一个行为类似于
NSMutableSet
对象的代理对象
- 它们返回一个行为类似于
mutableOrderedSetValueForKey:
和mutableOrderedSetValueForKeyPath:
- 它们返回一个行为类似于
NSMutableOrderedSet
对象的代理对象
- 它们返回一个行为类似于
- 访问非对象属性
- 默认键值编码实现使用
NSNumber
实例包装的标量类型 - 默认存取用于包装和展开常见
NSPoint
、NSRange
、NSRect
、和NSSize
结构 - 结构类型,可以包装在一个
NSValue
对象中
- 默认键值编码实现使用
- 验证属性
- 验证方法认为值对象有效并在
YES
不改变值或错误的情况下返回 - 验证方法认为值对象无效,但选择不更改它。在这种情况下,该方法返回
NO
并将错误引用(如果由调用者提供)设置为NSError
指示失败原因的对象 - 验证方法认为值对象无效,但会创建一个新的有效对象作为替换。在这种情况下,该方法返回
YES
同时保持错误对象不变。在返回之前,该方法修改值引用以指向新的值对象。当它进行修改时,该方法总是创建一个新对象,而不是修改旧对象,即使值对象是可变的
- 验证方法认为值对象有效并在
KVC
的存储过程:
【第一步】查找是否存在以下三种
setter
方法,查找顺序:set<Key>:
→_set<Key>
→setIs<Key>
key
是指成员变量名,首字母大小写需要符合KVC
的命名规范存在任意一种
setter
方法,直接设置属性的value
如果都不存在,进入【第二步】
【第二步】:查找
accessInstanceVariablesDirectly
方法的返回值返回
YES
,查找间接访问的实例变量进行赋值,查找顺序:_<key>
→_is<Key>
→<key>
→is<Key>
如果找到其中任意一个实例变量,可对其赋值
如果都未找到,进入【第三步】
返回
NO
,进入【第三步】
【第三步】如果
setter
方法或实例变量都没有找到,系统会调用该对象的setValue:forUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常
KVC
的读取过程:
【第一步】查找
getter
方法,查找顺序:get<Key>
→<key>
→is<Key>
→_<key>
如果找到,进入【第五步】
如果未找到,进入【第二步】
【第二步】查找
countOf<Key>
、objectIn<Key>AtIndex:
、<key>AtIndexes:
如果找到第一个或其他两个中的至少一个,则会创建一个响应所有
NSArray
方法的集合代理对象,并返回该对象,即:NSKeyValueArray
,属于NSArray
的子类。代理对象随后将接收到任何NSArray
消息转换为countOf<Key>
、objectIn<Key>AtIndex:
、<key>AtIndexes:
消息的某种组合,用来创建键值编码对象如果原始对象还实现了一个名为
get<Key>:range:
之类的可选方法,则代理对象也将在适当时使用该方法方法名的命名规则,要符合
KVC
的标准命名方法,包括方法签名
如果未找到,进入【第三步】
【第三步】查找以下几种方法,
countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
如果三个方法都找到,则会创建一个响应所有
NSSet
方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet
消息转换为countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
消息的某种组合,用于创建它的对象如果未找到,进入【第四步】
【第四步】查找
accessInstanceVariablesDirectly
方法的返回值返回
YES
,依次搜索_<key>
、_is<Key>
、<key>
或is<Key>
的实例变量找到实例变量,直接获取实例变量的值,进入【第五步】
未找到,进入【第六步】
返回
NO
,进入【第六步】
【第五步】根据搜索到的属性值的类型,返回不同的结果
如果是对象指针,则直接返回结果
如果是
NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回它如果是
NSNumber
不支持的标量类型,转换为NSValue
对象并返回该对象
【第六步】如果以上所有方法都失败,系统调用该对象的
valueForUndefinedKey:
方法,默认抛出NSUndefinedKeyException
类型的异常