
一.探索前需知
1.1 什么是KVC
KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的.
二.KVC的初探(KVC 的常见使用场景)
2.1 KVC对象属性的设值:在LGPerson.h里:

path的方法。所谓path,就是用点号连接的多层级的属性,比如student.name,student属性里的name属性。 LGPerson *person = [[LGPerson alloc] init];
// 1:Key-Value Coding (KVC) : 基本类型
[person setValue:@"KC" forKey:@"name"];
[person setValue:@19 forKey:@"age"];
[person setValue:@"酷C" forKey:@"myName"];
NSLog(@"%@ - %@ - %@",[person valueForKey:@"name"],[person valueForKey:@"age"],[person valueForKey:@"myName"]);
// 5:KVC - keypath 层层访问
LGStudent *student = [[LGStudent alloc] init];
student.subject = @"iOS";
person.student = student;
[person setValue:@"大师班" forKeyPath:@"student.subject"];
NSLog(@"%@",[person valueForKeyPath:@"student.subject"]);
2.2 KVC - 访问非对象属性如果想要访问对象属性(基本数据类型、结构体等)应该用NSValue 进行层包装,如:
// 4:KVC - 访问非对象属性
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"%@",reslut);
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"%f - %f - %f",th.x,th.y,th.z);
2.3 KVC 修改对象里的集合类型
person.array = @[@"1",@"2",@"3"];
// 由于不是可变数组 - 无法做到
// person.array[0] = @"100";
NSArray *array = [person valueForKey:@"array"];
// 用 array 的值创建一个新的数组
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"%@",[person valueForKey:@"array"]);
// KVC 的方式
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"100";
NSLog(@"%@",[person valueForKey:@"array"]);
2.4 KVC字典转模型操作
- (void)dictionaryTest{
NSDictionary* dict = @{
@"name":@"Cooci",
@"nick":@"KC",
@"subject":@"iOS",
@"age":@18,
@"length":@180
};
LGStudent *p = [[LGStudent alloc] init];
// 字典转模型
[p setValuesForKeysWithDictionary:dict];
NSLog(@"%@",p);
// 键数组转模型到字典
NSArray *array = @[@"name",@"age"];
NSDictionary *dic = [p dictionaryWithValuesForKeys:array];
NSLog(@"%@",dic);
}
三. KVC 的进阶
上面说了那么多,只是介绍了KVC的概念,已经平时开发中经常用到的使用场景,但是KVC的底层到底是如何进行的呢.(之前的博客文章,基本用源码来分析底层实现).但是KVC setValue:forKey: 会调用底层objc_setProperty_nonatomic方法,而这个方法是在LLVM汇编的时候执行的,所以对于一些不了解汇编指令的盆友就不太友好, 所以我们就换个思路通过阅读苹果官方文档来探索KVC的底层实现.
首先打开苹果开发者官网:(https://developer.apple.com/documentation/)
下面有个 Documentation Archive,点击进入:

在Documents里输入KeyValueCoding:
(https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/SearchImplementation.html#//apple_ref/doc/uid/20000955-CJBBBFFA)
Gettering Started里是关于KVC的介绍:
About Key-Value Coding
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.
(键值编码是由NSKeyValueCoding非正式协议启用的一种机制,对象采用该协议提供对其属性的间接访问。当一个对象与键值编码兼容时,它的属性可以通过一个简洁、统一的消息传递接口通过字符串参数寻址。这种间接访问机制补充了实例变量及其相关访问器方法提供的直接访问)
然后看到下面Accessor Search Patterns:

3.1 Search Pattern for the Basic Setter
The default implementation of setValue:forKey:, given key and value parameters as input, attempts to set a property named key to value (or, for non-object properties, the unwrapped version of value, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:
Look for the first accessor named
set<Key>:or_set<Key>, in that order. If found, invoke it with the input value (or unwrapped value, as needed) and finish.If no simple accessor is found, and if the class method
accessInstanceVariablesDirectlyreturnsYES, look for an instance variable with a name like_<key>,_is<Key>,<key>, oris<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.Upon finding no accessor or instance variable, invoke
setValue:forUndefinedKey:. This raises an exception by default, but a subclass ofNSObjectmay provide key-specific behavior.文档给出的意思就是:
- 按此顺序查找第一个名为set<Key>或_set<Key>的访问器。如果找到,使用输入值(或者根据需要使用unwrapped值)调用它并完成。(其实还有个 setIs<Key>)
- 如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名为“_<key>”、“_is<Key>”、“<key>”或“is<Key>”的实例变量。如果找到,直接用输入值(或未包装值)设置变量并完成。
- 在找不到访问器或实例变量时,调用setValue:forUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可能提供特定于键的行为。
看到这大家应该一目了然,那么下面我们就来验证下:
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"LG_Cooci" forKey:@"name"];
我们再看下LGPerson类:
LGPerson.h:

LGPerson.m:
#import "LGPerson.h"
@implementation LGPerson
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
@end
运行代码看输出:2020-02-13 21:23:34.799806+0800 002-KVC取值&赋值过程[43413:4290933] -[LGPerson setName:] - LG_Cooci.
接着把 - (void)setName:(NSString *)name 方法注释掉,再运行代码看输出:
2020-02-13 21:29:34.280334+0800 002-KVC取值&赋值过程[43433:4294790] -[LGPerson _setName:] - LG_Cooci.
接着把 - (void)setName:(NSString *)name 方法注释掉,再运行代码看输出:
2020-02-13 21:31:26.922422+0800 002-KVC取值&赋值过程[43451:4296659] -[LGPerson setIsName:] - LG_Cooci
说明setValue:for key:的默认实现第一步确实是访问setKey, 如果setKey 没实现就去访问_setKey,如果_setKey 没实现就去访问setIsKey.(依次进行的)
如果第一步中访问器都没实现会怎么样呢?
那就会来到第二步:如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名为“_<key>”、“_is<Key>”、“<key>”或“is<Key>”的实例变量。如果找到,直接用输入值(或未包装值)设置变量并完成。
我们也来验证一下:
类方法accessInstanceVariablesDirectly要返回YES, 我就返回个NO.看看会出现啥问题,
#import "LGPerson.h"
@implementation LGPerson
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
发现程序会崩溃,'NSUnknownKeyException', reason: '[<LGPerson 0x6000004c8300> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
原来accessInstanceVariablesDirectly 是判断关闭或开启实例变量赋值,你只有返回YES,它才能够访问实例变量.
LGPerson.h:
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@property (nonatomic, strong) NSArray *arr;
@property (nonatomic, strong) NSSet *set;
@property (nonatomic, strong) NSMutableArray *namesArrM;
@property (nonatomic, strong) NSMutableSet *namesSetM;
@property (nonatomic, strong) NSMutableOrderedSet *orderedSetM;
@end
LGPerson.m:
#import "LGPerson.h"
@implementation LGPerson
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
@endViewController里代码:
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"LG_Cooci" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
// NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
// NSLog(@"%@-%@",person->name,person->isName);
// NSLog(@"%@",person->isName);
运行看结果:2020-02-13 21:50:56.090657+0800 002-KVC取值&赋值过程[43521:4311589] LG_Cooci-(null)-(null)-(null),看来只能访问_name 实例变量有值,说明setValue 优先找的是_name 实例变量.
接着我们LGPerson 中_name实例变量注释掉,
然后输出NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);其它注释看啥结果:2020-02-13 21:56:58.163683+0800 002-KVC取值&赋值过程[43558:4316299] LG_Cooci-(null)-(null). 看来_name 实例变量没有只能访问_isName 实例变量有值,说明_name 实例变量没有 setValue 优先找的是 _isName 实例变量.
接下来一个思路注释掉 _isName 实例变量 在进行打印输出......最后的最后得出的结论就是:
如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名为“_<key>”、“_is<Key>”、“<key>”或“is<Key>”的实例变量。如果找到,直接用输入值(或未包装值)设置变量并完成。和官网文档说的是一模模一样样的.
至于第三步应该不用验证了吧,相信朋友们都和熟悉了吧就是:在找不到访问器或实例变量时,调用setValue:forUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可能提供特定于键的行为. (我们刚开始开发时,经常会遇到这个异常错误).
3.2 Search Pattern for the Basic Getter
The default implementation of valueForKey:, given a key parameter as input, carries out the following procedure, operating from within the class instance receiving the valueForKey: call.
Search the instance for the first accessor method found with a name like
get<Key>,<key>,is<Key>, or_<key>, in that order. If found, invoke it and proceed to step 5 with the result. Otherwise proceed to the next step.If no simple accessor method is found, search the instance for methods whose names match the patterns
countOf<Key>andobjectIn<Key>AtIndex:(corresponding to the primitive methods defined by theNSArrayclass) and<key>AtIndexes:(corresponding to theNSArraymethodobjectsAtIndexes:).If the first of these and at least one of the other two is found, create a collection proxy object that responds to all
NSArraymethods and return that. Otherwise, proceed to step 3.The proxy object subsequently converts any
NSArraymessages it receives to some combination ofcountOf<Key>,objectIn<Key>AtIndex:, and<key>AtIndexes:messages to the key-value coding compliant object that created it. If the original object also implements an optional method with a name likeget<Key>:range:, the proxy object uses that as well, when appropriate. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSArray, even if it is not.If no simple accessor method or group of array access methods is found, look for a triple of methods named
countOf<Key>,enumeratorOf<Key>, andmemberOf<Key>:(corresponding to the primitive methods defined by theNSSetclass).If all three methods are found, create a collection proxy object that responds to all
NSSetmethods and return that. Otherwise, proceed to step 4.This proxy object subsequently converts any
NSSetmessage it receives into some combination ofcountOf<Key>,enumeratorOf<Key>, andmemberOf<Key>:messages to the object that created it. In effect, the proxy object working together with the key-value coding compliant object allows the underlying property to behave as if it were anNSSet, even if it is not.If no simple accessor method or group of collection access methods is found, and if the receiver's class method
accessInstanceVariablesDirectlyreturnsYES, search for an instance variable named_<key>,_is<Key>,<key>, oris<Key>, in that order. If found, directly obtain the value of the instance variable and proceed to step 5. Otherwise, proceed to step 6.If the retrieved property value is an object pointer, simply return the result.
If the value is a scalar type supported by
NSNumber, store it in anNSNumberinstance and return that.If the result is a scalar type not supported by NSNumber, convert to an
NSValueobject and return that.If all else fails, invoke
valueForUndefinedKey:. This raises an exception by default, but a subclass ofNSObjectmay provide key-specific behavior.
话不多说,开始验证:
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"LG_Cooci" forKey:@"name"];
// 2: KVC - 取值的过程
// person->_name = @"_name";
// person->_isName = @"_isName";
// person->name = @"name";
// person->isName = @"isName";
NSLog(@"取值:%@",[person valueForKey:@"name"]);
LGPerson.m:
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,
- (NSString *)getName{
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
运行代码,看控制台打印:
2020-02-14 10:36:31.988665+0800 002-KVC取值&赋值过程[45573:4395417] 取值:getName. 说明valueForKey 过程中首先访问访问器方法get<Key>.
接着把方法- (NSString *)getName注释掉,在运行程序,后台打印如下:
2020-02-14 10:40:06.743185+0800 002-KVC取值&赋值过程[45614:4414017] 取值:name.说明实例方法get<Key>不存在,会访问访问器方法<key>.
然后再把方法- (NSString *)name注释掉,在运行程序......
最后得出了结论:执行valueForKey会在实例中搜索找到的第一个访问器方法,该方法的名称如get<Key>、<key>、is<Key>、或者_<key>.然后跳转第五步.
5.如果该值是NSNumber支持的标量类型,请将其存储在NSNumber实例中并返回该实例。 如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回该对象。
接着我们来看第二步和第三步:
2.如果找不到简单的访问器方法,请在实例中搜索名称与模式countOf<Key>和objectIn<Key>AtIndex:(对应于NSArray类定义的基元方法)和<Key>AtIndex:(对应于NSArray方法objectsAtIndexes:)匹配的方法。 如果找到其中的第一个和其他两个方法中的至少一个,则创建一个响应所有NSArray方法的集合代理对象并返回该对象。否则,继续执行步骤3。 代理对象随后将其接收到的任何NSArray消息转换为countOf<Key>、objectIn<Key>AtIndex:、和<Key>AtIndexes:消息的组合,并将其转换为创建该对象的键值编码兼容对象。如果原始对象还实现了一个名为get<Key>:range:(get<Key>:range:)的可选方法,则代理对象在适当时也会使用该方法。实际上,代理对象与密钥值编码兼容对象一起工作,允许底层属性的行为如同它是NSArray,即使它不是NSArray。
3. 如果找不到简单的访问器方法或数组访问方法组,请查找名为countOf<Key>、enumeratorOf<Key>和memberOf<Key>的三个方法(对应于NSSet类定义的基本方法)。
如果找到这三个方法,则创建一个集合代理对象,该对象响应所有NSSet方法并返回该对象。否则,继续执行步骤4。
此代理对象随后将接收到的任何NSSet消息转换为countOf<Key>、enumeratorOf<Key>和memberOf<Key>:消息的组合,并将其转换为创建它的对象。实际上,代理对象与键值编码兼容对象一起工作,允许底层属性的行为如同它是NSSet,即使它不是NSSet。
什么意思呢?解释一下:
LGPerson *person = [[LGPerson alloc] init];
// 3: KVC - 集合类型
person.arr = @[@"pen0", @"pen1", @"pen2", @"pen3"];
NSArray *array = [person valueForKey:@"pens"];
NSLog(@"%@",[array objectAtIndex:1]);
NSLog(@"%d",[array containsObject:@"pen1"]);
[person valueForKey:@"pens"] 首先会去搜索访问器方法- get<Key>, <key>, is<Key>, or _<key>. 但是在这里我们没有去实现.
它会去搜索- (NSUInteger)countOfPens 和 - (id)objectInPensAtIndex:(NSUInteger)index这两个方法, 看是否有实现.
//MARK: - 集合类型的走
// 个数
- (NSUInteger)countOfPens{
NSLog(@"%s",__func__);
return [self.arr count];
}
// 获取值
- (id)objectInPensAtIndex:(NSUInteger)index {
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"pens %lu", index];
}
当array调用objectAtIndex 会调用到objectInPensAtIndex这里来,我们来看下NSLog(@"%@",[array objectAtIndex:1]).
输出:2020-02-14 11:03:00.250697+0800 002-KVC取值&赋值过程[45706:4427980] pens 1.
而array调用count 会调用到countOfPens这里来 NSLog(@"%lu",(unsigned long)[array count]);
输出:2020-02-14 11:13:38.404264+0800 002-KVC取值&赋值过程[45741:4436685] 4
-get<Key>:range://不是必须实现的,但实现后可以提高性能,其对应于 NSArray 方法 getObjects:range.
如果在上面如果找不到简单的访问器方法或数组访问方法组,那么会同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,和上面一样,给这个代理集合发NSSet的消息,就会以countOf<Key>,enumeratorOf<Key>,memberOf<Key>组合的形式调用.
// set 集合
person.set = [NSSet setWithArray:person.arr];
NSSet *set = [person valueForKey:@"books"];
[set enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(@"set遍历 %@",obj);
}];
LGPerson.m:
//MARK: - set
// 个数
- (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];
}
其实第二步和第三步是对集合、数组类型做了特殊的处理,一般开发中很少会用到.接下来来到第四步:
4.如果找不到简单的访问器方法或集合访问方法组,并且如果接收方的类方法accessInstanceVariablesDirectly返回YES,则按该顺序搜索名为_<key>、_is<Key>、<key>或is<Key>的实例变量。如果找到,直接获取实例变量的值并继续执行步骤5。否则,继续执行步骤6。
// 1: KVC - 设置值的过程
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 设置值的过程
[person setValue:@"LG_Cooci" forKey:@"name"];
// 2: KVC - 取值的过程
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@"取值:%@",[person valueForKey:@"name"]);
LGPersson.h:
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
LGPerson.m:
#pragma mark - 关闭或开启实例变量赋值
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
//MARK: - setKey. 的流程分析
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)_setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//- (void)setIsName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
//MARK: - valueForKey 流程分析 - get<Key>, <key>, is<Key>, or _<key>,
//- (NSString *)getName{
// return NSStringFromSelector(_cmd);
//}
//- (NSString *)name{
// return NSStringFromSelector(_cmd);
//}
//
//- (NSString *)isName{
// return NSStringFromSelector(_cmd);
//}
//
//- (NSString *)_name{
// return NSStringFromSelector(_cmd);
//}
运行看输出:2020-02-14 11:34:01.280728+0800 002-KVC取值&赋值过程[45790:4450022] 取值:_name.
说明找不到简单的访问器方法或集合访问方法组,并且如果接收方的类方法accessInstanceVariablesDirectly返回YES,会搜索名为_<key>的实例变量,
接着我们注释 person->_name = @"_name"; 运行看输出:2020-02-14 11:34:01.280728+0800 002-KVC取值&赋值过程[45790:4450022] 取值:_isName.
接着我们再注释person->_isName = @"_isName"......
最后得出结论:如果找不到简单的访问器方法或集合访问方法组,并且如果接收方的类方法accessInstanceVariablesDirectly返回YES,则按该顺序搜索名为_<key>、_is<Key>、<key>或is<Key>的实例变量.
第6步就是 上面所有的访问器方法或集合访问方法组、实例变量都找不到,会调用valueForUndefinedKey:。默认情况下,这会引发异常.
四.自定义KVC
4.1 自定义思路
系统的KVC是用NSObject的类别实现的,我们要自定义KVC无非也是系统的思路,实现个自定义KVC的类别.
4.11 setValue:
1.判断是否有找到相关方法 set<Key> _set<Key> setIs<Key>的方法.2.判断是否能够直接赋值实例变量 (accessInstanceVariablesDirectly方法是否返回YES).3.找相关实例变量进行赋值4.如果都找不到抛出异常setValueForUndefinedKey.
4.12 valueForKey
1. 找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex.2.判断是否能够直接赋值实例变量 (accessInstanceVariablesDirectly方法是否返回YES).3.找相关实例变量进行赋值.4.如果都找不到抛出异常valueForUndefinedKey.
4.2 伪代码实现
@implementation NSObject (LGKVC)
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// 1:非空判断一下
if (key == nil || key.length == 0) return;
// 2:找到相关方法 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:判断是否能够直接赋值实例变量
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>
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];
}
- (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 *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",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(countOfKey)]){
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 *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
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 @"";
}
#pragma mark - 相关方法
- (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;
}
@end五.总结
KVC在设值过程中:1.首先顺序查找第一个名为set<Key>、_set<Key>、 setIs<Key>的访问器. 2.如果找不到简单的访问器,并且类方法accessInstanceVariablesDirectly返回YES,则按该顺序查找名为“_<key>”、“_is<Key>”、“<key>”或“is<Key>”的实例变量. 3.在找不到访问器或实例变量时,调用setValue:forUndefinedKey.
KVC在取值过程中:1.在实例中搜索找到的访问器方法,该方法的名称如get<Key>、<key>、is<Key>、或者_<key>.如果有就跳到第五步. 2.是否是NSArray类型判断. 3.是否是NSSet判断. 4.判断是否能够直接赋值实例变量 ,找相关实例变量进行赋值. 5.细节处理 如果该值是NSNumber支持的标量类型,请将其存储在NSNumber实例中并返回该实例。 如果结果是NSNumber不支持的标量类型,则转换为NSValue对象并返回该对象. 6.如果访问器或实例变量都找不到调用valueForUndefinedKey.