一.探索前需知
html
1.1 什么是KVCapi
KVC(Key-value coding)键值编码,就是指iOS的开发中,能够容许开发者经过Key名直接访问对象的属性,或者给对象的属性赋值。而不须要调用明确的存取方法。这样就能够在运行时动态地访问和修改对象的属性。而不是在编译时肯定,这也是iOS开发中的黑魔法之一。不少高级的iOS开发技巧都是基于KVC实现的.数组
二.KVC的初探(KVC 的常见使用场景)bash
2.1 KVC对象属性的设值:在LGPerson.h里:
app
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 进行层包装,如:ide
// 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字典转模型操做ui
- (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 的进阶this
上面说了那么多,只是介绍了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的介绍:
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:
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 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.
Upon finding no accessor or instance variable, invoke setValue:forUndefinedKey:
. This raises an exception by default, but a subclass of NSObject
may provide key-specific behavior.
文档给出的意思就是:
看到这你们应该一目了然,那么下面咱们就来验证下:
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);
//}
@end复制代码
ViewController里代码:
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的子类可能提供特定于键的行为. (咱们刚开始开发时,常常会遇到这个异常错误).
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>
and objectIn<Key>AtIndex:
(corresponding to the primitive methods defined by the NSArray
class) and <key>AtIndexes:
(corresponding to the NSArray
method objectsAtIndexes:
).
If the first of these and at least one of the other two is found, create a collection proxy object that responds to all NSArray
methods and return that. Otherwise, proceed to step 3.
The proxy object subsequently converts any NSArray
messages it receives to some combination of countOf<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 like get<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 an NSArray
, 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>
, and memberOf<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 all NSSet
methods and return that. Otherwise, proceed to step 4.
This proxy object subsequently converts any NSSet
message it receives into some combination of countOf<Key>
, enumeratorOf<Key>
, and memberOf<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 an NSSet
, 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>
, or is<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 an NSNumber
instance and return that.
If the result is a scalar type not supported by NSNumber, convert to an NSValue
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.
话很少说,开始验证:
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.