在日常的编码中我们会经常用到KVC,今天从以下3个方面来探索下经常使用的KVC。
- 1.kvc赋值
- 2.kvc取值
- 3.自定义kvc
1.KVC赋值
首先来看这样一个例子
@interface MGPerson : NSObject {
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
#import "MGPerson.h"
@implementation MGPerson
//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);
}
- (void)_setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
@end
这里我们创建了一个类MGPerson
并在其中写入了4个成员变量:_name
,_isName
,name
,isName
,以及如代码所示这样一些方法;
然后我们在ViewController
中去调用我们写好的类,并通过setValue:<#(nullable id)#> forKey:<#(nonnull NSString *)#>
去给我们声明的成员变量赋值,最后来看下打印结果
- (void)viewDidLoad {
[super viewDidLoad];
MGPerson *person = [[MGPerson alloc] init];
[person setValue:@"MG" forKey:@"name"];
NSLog(@"p->name == %@",person->name);
NSLog(@"p->_name == %@",person->_name);
NSLog(@"p->_isName == %@",person->_isName);
NSLog(@"p->isName == %@",person->isName);
}
打印结果
-[MGPerson setName:] - MG
p->name == (null)
p->_name == (null)
p->_isName == (null)
p->isName == (null)
从打印结果中我们看到这里只调用了setName
方法,符合我们的预期,那么假如注释掉setName
方法呢
@implementation MGPerson
//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);
}
// 没有调用
- (void)_setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
@end
MGPerson.m
代码如上
打印结果
-[MGPerson _setName:] - MG
p->name == (null)
p->_name == (null)
p->_isName == (null)
p->isName == (null)
这次却走了_setName
,和我们的预期有些出入,接下来我们依次把.m
文件的方法注释掉看下会发生什么
注释掉_setName
的打印结果
-[MGPerson setIsName:] - MG
p->name == (null)
p->_name == (null)
p->_isName == (null)
p->isName == (null)
这次却走了setIsName
方法,再注释掉setIsName
看看打印结果
p->name == (null)
p->_name == MG
p->_isName == (null)
p->isName == (null)
注释掉_name
的打印结果
@interface MGPerson : NSObject {
@public
// NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
MGPerson *person = [[MGPerson alloc] init];
[person setValue:@"MG" forKey:@"name"];
NSLog(@"p->name == %@",person->name);
// NSLog(@"p->_name == %@",person->_name);
NSLog(@"p->_isName == %@",person->_isName);
NSLog(@"p->isName == %@",person->isName);
}
打印结果
p->name == (null)
p->_isName == MG
p->isName == (null)
通过打印结果,我们发现首先会赋值给_name
,如果没有_name
则会赋值给_isName
,如果没有_isName
呢?接下来采用同样的方式把_isName
也给注释掉
打印结果
p->name == MG
p->isName == (null)
这次就赋值给了name
了,再把name
给注释掉
打印结果
p->isName == MG
这次就赋值给了isName
了。
总结:
通过KVC赋值成员变量,有两个步骤
Step1:set<key>
->_set<key>
->setIs<key>
如果这些都没实现那么会走到Step2
Step2:_<key>
->_is<key>
-><key>
->is<key>
这是为什么呢,我们去查看下官方文档是怎么解释的
苹果KVC官方文档
在文档中发现这样一段描述
Search Pattern for the Basic Setter
The default implementation ofsetValue:forKey:
, givenkey
andvalue
parameters as input, attempts to set a property namedkey
tovalue
(or, for non-object properties, the unwrapped version ofvalue
, as described in Representing Non-Object Values) inside the object receiving the call, using the following procedure:
1.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.
2.If no simple accessor is found, and if the class method accessInstanceVariablesDirectly returns YES, look for an instance variable with a name like _<key>, _is<Key>, <key>, or is<Key>, in that order. If found, set the variable directly with the input value (or unwrapped value) and finish.
3.Upon finding no accessor or instance variable, invokesetValue:forUndefinedKey:
. This raises an exception by default, but a subclass ofNSObject
may provide key-specific behavior.
大致意思就是:
1.按顺序查找名为set<Key>
或_set<Key>
的第一个访问器。如果找到,使用输入值(或根据需要展开的值)调用它并完成。
2.如果找不到简单访问器,并且类方法accessInstanceVariablesDirectly返回YES,请按顺序查找名为_<key>
、_is<key>
、<key>
或is<key>
的实例变量。如果找到,直接用输入值(或展开的值)设置变量并完成。
3.如果找不到访问器或实例变量,请调用setValue:forUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可能会提供特定于键的行为。
从官方文档中我们得到了解释,不过对比我们的实例,官方文档在第一条中漏掉了setIs<key>
.
通过官方文档以及上面的代码示例我们就能得到这样一张流程图
2.KVC取值
苹果的官方文档是这样说的
Search Pattern for the Basic Getter
The default implementation of
valueForKey:
, given akey
parameter as input, carries out the following procedure, operating from within the class instance receiving thevalueForKey:
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 theNSArray
class) and<key>AtIndexes:
(corresponding to the NSArray methodobjectsAtIndexes:
).
If the first of these and at least one of the other two is found, create a collection proxy object that responds to allNSArray
methods and return that. Otherwise, proceed to step 3.The proxy object subsequently converts anyNSArray
messages 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 the NSSet class).
If all three methods are found, create a collection proxy object that responds to allNSSet
methods and return that. Otherwise, proceed to step 4.
This proxy object subsequently converts anyNSSet
message 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 accessInstanceVariablesDirectly returns
YES
, 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 byNSNumber
, store it in anNSNumber
instance and return that.
If the result is a scalar type not supported by NSNumber, convert to anNSValue
object and return that.- If all else fails, invoke valueForUndefinedKey:. This raises an exception by default, but a subclass of
NSObject
may provide key-specific behavior.
大致,首先通过
get<Key>
-><key>
->is<Key>
->_<key>
这个几个方法去获取值,如果获取不到
再在_<key>
->_is<Key>
-><key>
->is<Key>
成员变量中去获取,如果还是获取不到
valueForUndefinedKey
抛出异常
@interface MGPerson : NSObject {
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation MGPerson
//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);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
MGPerson *person = [[MGPerson alloc] init];
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@"取值:%@",[person valueForKey:@"name"]);
}
通过这样一个实例来验证,查看下打印结果
取值:getName
这个打印结果说明,调用了getName
方法
我们注释掉getName
看看会不会调用name
方法
取值:name
当注释掉getName
方法时,结果与预想一致调用了name
方法
接下来依次注释掉name
,isName
,_name
,看看下结果
//注释掉 -(NSString *)name
取值:isName
//符合预期
//注释掉 -(NSString *) isName
取值:_name
//符合预期
//注释掉 -(NSString *) _name
取值:_name
//符合预期
方法的查找流程已经验证完毕,接下来验证下成员变量的流程,依次注释掉_name
,_isName
,name
,isName
;
//注释掉 _name成员变量
取值:_isName
//符合预期
//注释掉 _isName成员变量
取值: name
//符合预期
//注释掉 name成员变量
取值: isName
//符合预期
//注释掉 isName成员变量
程序崩掉
//符合预期
3.自定义KVC
自定义KVC其实就是实现-(void)setValue:(nullable id)value forKey:(NSString *)key
与-(id)valueForKey:(NSString *)key
方法
3.1实现set
方法
- 1.对传入的
key
进行非空判断 - 2.构建setter即
set<key>
或者_set<key>
- 3.判断是否能够直接赋值给实例变量 ---- accessInstanceVariablesDirectly 返回yes
- 4.设置
ivar
的值 - 5.如果找不到相关实例则抛出异常
- (void)mg_setValue:(nullable id)value forKey:(NSString *)key {
//自定义KVC
//1.判断key
if (key.length == 0 || key == nil) return;
//2.构建setter set<key> or _set<key>
//key的首字母大写
NSString *newKey = key.capitalizedString;
//拼接出方法名
NSString *setKey = [NSString stringWithFormat:@"set%@:",newKey];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",newKey];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",newKey];
//调用方法
if ([self mg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"------- %@ ---------",setKey);
return;
}else if ([self mg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"------- %@ ---------",_setKey);
return;
}else if ([self mg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"------- %@ ---------",setIsKey);
return;
}
//3. 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
if ([self.class accessInstanceVariablesDirectly] == NO) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
//4.变量赋值
//获取成员变量名
NSMutableArray *nArray = [self getIvarListNames];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",newKey];
NSString *isKey = [NSString stringWithFormat:@"is%@",newKey];
if ([nArray containsObject:_key]) {
//获取相应的ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
//对相应的 ivar 设置值
object_setIvar(self, ivar, value);
return;
} else if ([nArray containsObject:_isKey]) {
//获取相应的ivar
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
//对相应的 ivar 设置值
object_setIvar(self, ivar, value);
return;
} else if ([nArray containsObject:newKey]) {
//获取相应的ivar
Ivar ivar = class_getInstanceVariable([self class], newKey.UTF8String);
//对相应的 ivar 设置值
object_setIvar(self, ivar, value);
return;
} else if ([nArray containsObject:isKey]) {
//获取相应的ivar
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
//对相应的 ivar 设置值
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];
}
- (BOOL)mg_performSelectorWithMethodName:(NSString *)methodName value:(id)value {
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
[self performSelector:NSSelectorFromString(methodName) withObject:value];
return YES;
}
return NO;
}
//获取成员变量名列表
- (NSMutableArray *)getIvarListNames {
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;
}
3.2实现get
方法
- 1.对传入的
key
进行非空判断 - 2.找到相关方法
get<Key>
<key>
countOf<Key>
objectIn<Key>AtIndex
- 3:判断是否能够直接赋值实例变量
- 4.找相关实例变量进行赋值
- (id)mg_valueForKey:(NSString *)key {
//1:key判空
if (key == nil || key.length == 0) return nil;
//2:找到相关方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
//key 首字母大写处理
NSString *newKey = key.capitalizedString;
NSString *getKey = [NSString stringWithFormat:@"get%@",newKey];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",newKey];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",newKey];
//调用方法
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(newKey)]) {
return [self performSelector:NSSelectorFromString(newKey)];
}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;
}
}
//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.找相关实例变量进行赋值
// 定义一个收集实例变量的可变数组
NSMutableArray *nArray = [self getIvarListNames];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",newKey];
NSString *isKey = [NSString stringWithFormat:@"is%@",newKey];
if ([nArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([nArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([nArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([nArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}