首先放出官方文档的连接:(官网很重要哦)
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueCoding/index.html#//apple_ref/doc/uid/10000107-SW1
KVC 是什么?
kvc 键值编码,是通过字符串“key” 来间接访问对象属性的一种机制。相关的API 在 NSObject 的分类 NSKeyValueCoding
中。
- 通过
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;
- 属性,是否可以 开启 间接访问变量的方式
//默认为YES(只读)
@property (class, readonly) BOOL accessInstanceVariablesDirectly;
//通过类方法改变其返回值
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
- 异常处理
//验证是否键值匹配
//调用原理:会先找你对象中是否实现了 -(BOOL)validate<Key>:error: 方法
//如果实现了,就根据方法里面的实现逻辑返回YES 或NO,没有实现,以下方法系统默认返回YES。
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//与上同理,不过是 对属性的属性的一个验证
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
// 在取值的时候找不到对应的key,防止找不到崩溃
- (nullable id)valueForUndefinedKey:(NSString *)key;
//在设置值的时候找不到对应的key
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//防止设置的是空值而发生崩溃
- (void)setNilValueForKey:(NSString *)key;
- 为对象批量设置属性值,或批量获取
//指定一组key,找到对应的属性值,以字典的形式返回
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//对一组key:value值,为对象批量设置
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
- 访问可变的集合属性
//可变数组访问
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//NSSet里面的元素是无序的,不能通过索引访问,且元素不能重复,与NSArray中的元素不同(有序、可通过索引访问、可重复);
//可变集合是NSSet的子类,跟NSSet不一样的地方就在于:NSMutableSet中的元素可以修改(增加、删除、替换)
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
//NSOrderedSet:跟NSSet中不一样的地方在于:有序,可以通过索引访问,元素不能重复;有序可变集合是NSOrderedSet的子类,与NSOrderedSet不同就在于可以增删替换操作。
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key
----------------------------------以下对应路由访问----------------------------------------
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
原理分析
-
setValue:forKey
先看一下官方文档:
-
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
returnsYES
, 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 ofNSObject
may provide key-specific behavior.
翻译:
1、先按顺序查找set<Key>
,_set<Key>
方法,如果第一个方法实现了,就会直接调用第一个,那么不会调用第二个。第一个没有实现才会调用第二个方法
2、如果上述方法都没有实现,那么会查看类方法accessInstanceVariablesDirectly
,是否返回YES
,如果返回YES
,那么代表可以间接访问该类的成员变量。_<Key>
,_is<Key>
,<key>
,<isKey>
,按顺序查找,规则和上述一样。如果找到,就直接给该属性赋值。如果没有找到就跳到第3步
3、如果上述变量也没有找到,如果类中实现了异常方法 setValue:forUndefinedKey:
,则会被调用,否则会抛出异常。
代码验证setter
@interface HLPerson : NSObject
{
#pragma mark - group4
/**
如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
直接返回该变量的值
*/
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
// NSArray *_mySons;
}
//@property(nonatomic, copy) NSString *name;
@property(nonatomic, strong) NSArray *sons;
@end
@interface HLPerson ()
@end
@implementation HLPerson
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
//yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
//no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
//为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
/**
如果实现了 set<Key>,_set<Key> 方法,会按顺序优先调用
如果没有实现,并且 accessInstanceVariablesDirectly 为 Yes
就会给 _<key>, _is<Key>, <key>, or is<Key>, 变量赋值
如果为 No,那么会触发 setValue:forUndefinedKey: 方法
*/
- (void)setName:(NSString *)name {
NSLog(@"%s",__func__);
}
- (void)_setName:(NSString *)name {
NSLog(@"%s",__func__);
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"%s",__func__);
}
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"%@的值不能为空",key);
}
@end
我们先对 name
进行访问:
- (void)viewDidLoad {
[super viewDidLoad];
[self arrayTest];
// [self arrayNesting];
HLPerson *person = [[HLPerson alloc]init];
[person setValue:@"name" forKey:@"name"];
}
打印结果:
2021-01-25 15:37:23.886114+0800 KVC--001[11876:880083] -[HLPerson setName:]
再注释掉- (void)setName:name
2021-01-25 15:38:46.948742+0800 KVC--001[11901:881343] -[HLPerson _setName:]
将set 方法都注释掉
HLPerson *person = [[HLPerson alloc]init];
[person setValue:@"name" forKey:@"name"];
NSLog(@"name = %@",person->name);
NSLog(@"_name = %@",person->_name);
NSLog(@"_isName = %@",person->_isName);
NSLog(@"isName = %@",person->isName);
打印结果:
2021-01-25 15:41:37.897000+0800 KVC--001[11950:883978] name = (null)
2021-01-25 15:41:37.897108+0800 KVC--001[11950:883978] _name = name
2021-01-25 15:41:37.897208+0800 KVC--001[11950:883978] _isName = (null)
2021-01-25 15:41:37.897312+0800 KVC--001[11950:883978] isName = (null)
将所有变量注释掉
2021-01-25 15:43:11.087560+0800 KVC--001[11992:885503] -[HLPerson setValue:forUndefinedKey:]
以上过程可以自己去验证一下。
KVC 取值过程 valueForKey
上官方文档:
-
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](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSArrayClassCluster/Description.html#//apple_ref/occ/cl/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 all
NSArray
methods and return that. Otherwise, proceed to step 3.The proxy object subsequently converts any
NSArray
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](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/Foundation/Classes/NSSetClassCluster/Description.html#//apple_ref/occ/cl/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 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](https://developer.apple.com/library/archive/documentation/LegacyTechnologies/WebObjects/WebObjects_3.5/Reference/Frameworks/ObjC/EOF/EOControl/Classes/NSObjectAdditions/Description.html#//apple_ref/occ/clm/NSObject/accessInstanceVariablesDirectly)
returnsYES
, 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 anNSNumber
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:](https://developer.apple.com/documentation/objectivec/nsobject/1413457-value)
. This raises an exception by default, but a subclass ofNSObject
may provide key-specific behavior.
翻译:
1、取值时,第一步会按照get<Key>, <key>, is<Key>, or _<key>,
这个顺序来查找对应的实例方法。如果找到跳5,找不到 下一步
2、如果第一步中的方法没有找到,kvc会查找countOf<Key>
和objectIn<Key>AtIndex
或者<key>AtIndexes
.(当然第二步其实是针对要访问的是数组集合,因为kvc并不知道你要访问的是什么,所以会根据这个过程先去查找,如果我们要访问集合,在需要的时候可以实现这几个方法)。- 如果找到countOf<key>和其他两个方法中的一个,则会创建一个相应所有NSArray方法的
集合代理对象
,并且返回该对象,即NSKeyValueArray
。是NSArray
的子类。代理对象将接受到的所有NSArray消息,都转换为countOf<Key>,objectIn<Key> AtIndex:
和<key>AtIndexes:
消息的某种组合(例如,调用[array count],就会触发countOf<Key>
,遍历array,就会调用countOf<Key>,objectIn<Key> AtIndex:
)用来创建键值编码的对象。如果类内实现了get<Key>:range:
之类的可选方法,代理对象也会在适当的时候使用该方法。如果没有找到这三个方法,那么进入下一步。
3、如果没有找到上面的方法,会同时查找countOf <Key>,enumeratorOf<Key>和memberOf<Key>
,这三个方法必须同时存在才行。 如果都找到,会创建一个响应所有nsset方法的集合代理对象,并返回该对象,此代理对象,随后将其收到的所有NSSet消息转换为countOf <Key>,enumeratorOf<Key>和memberOf<Key>
消息的某种组合,用于创建他的对象。没有找到,会进入下一步。
4、如果上述方法都没有找到,检查类方法accessInstanceVariablesDirectly
是否返回yes,如果是,那么会查找_<key>,_is<Key>,<key>或is<Key>
的实例变量。如果找到,直接返回该变量的值,没有找到就跳到6.
5、根据搜索到的成员变量值的类型,返回不同的结果
1>如果是对象指针
,则直接返回结果
2>如果是NSNumber
支持的标量类型,则将其存储在NSNumber
实例中并返回它
3>如果是NSNumber
不支持的标量类型,则转换为NSValue
对象,并返回
6、若上述方法都查找失败,则会执行valueForUndefinedKey:
,默认抛出NSUndefinedKeyException
异常.
- 如果找到countOf<key>和其他两个方法中的一个,则会创建一个相应所有NSArray方法的
代码实现
@interface HLPerson ()
@end
@implementation HLPerson
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
//yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
//no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
//为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
// valueForKey: 方法调用顺序([person valueForKey:@"name"];)
#pragma mark - group1
/**
1、如果实现了getName,会先调用getName 方法,
2、如果没有实现getName方法,实现了name 方法,则调用name
3、如果明确声明了属性,就不会调用isName方法,否则会调用
*/
- (NSString *)getName {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)name {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
/// 注意:如果明确声明了name 属性,是不会调用 isName 方法的
- (NSString *)isName {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
- (NSString *)_name {
NSLog(@"%s",__func__);
return NSStringFromSelector(_cmd);
}
/**
如果以上步骤都没有实现,那么会调用以下方法,表示没有找到对应的key
*/
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"没有找到%@",key);
return key;
}
@end
调用:
HLPerson *person = [[HLPerson alloc]init];
NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
打印:
2021-01-25 17:34:11.507661+0800 KVC--001[12719:939227] -[HLPerson getName]
2021-01-25 17:34:11.507772+0800 KVC--001[12719:939227] valueForKey = getName
去掉所有的get方法
HLPerson *person = [[HLPerson alloc]init];
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
打印结果:
2021-01-25 17:37:04.635216+0800 KVC--001[12760:941948] valueForKey = _name
再注释掉所有的成员变量
@interface HLPerson : NSObject
{
#pragma mark - group4
/**
如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
直接返回该变量的值
*/
@public
// NSString *_name;
// NSString *_isName;
// NSString *name;
// NSString *isName;
// NSArray *_mySons;
}
//@property(nonatomic, copy) NSString *name;
@property(nonatomic, strong) NSArray *sons;
@end
HLPerson *person = [[HLPerson alloc]init];
NSLog(@"valueForKey = %@",[person valueForKey:@"name"]);
打印结果:
2021-01-25 17:38:26.310139+0800 KVC--001[12790:943271] 没有找到name
2021-01-25 17:38:26.310269+0800 KVC--001[12790:943271] valueForKey = name
对于集合属性
@interface HLPerson : NSObject
{
#pragma mark - group4
/**
如果以下步骤方法都不存在,并且‘accessInstanceVariablesDirectly’返回YES
那么会按顺序寻找_<key>, _is<Key>, <key>, or is<Key>,实例变量,如果存在
直接返回该变量的值
*/
@public
// NSString *_name;
// NSString *_isName;
// NSString *name;
// NSString *isName;
// NSArray *_mySons;
}
//@property(nonatomic, copy) NSString *name;
@property(nonatomic, strong) NSArray *sons;
@end
#import "HLPerson.h"
@interface HLPerson ()
@end
@implementation HLPerson
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
//yes:表示 以下那些方法没有实现,可以访问 _key,_isKey,key,isKey 成员变量
//no,表示如果上述方法没有实现,也不能去访问 _key,_isKey,key,isKey 成员变量
//为no,并且方法都没有实现,那么会走到 valueForUndefinedKey 方法
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
- (NSUInteger)countOfMySons {
NSLog(@"%s",__func__);
return _sons.count;
}
//2, 相较于 3 会优先调用
- (id)objectInMySonsAtIndex:(NSUInteger)index {
NSLog(@"mySons = %ld",index);
return [_sons objectAtIndex:index];
}
//- validateValue:forKey:error: 验证kvc 是否可用时,会调用下面的方法
- (BOOL)validateMySons:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ //在implementation里面加这个方法,它会验证是否设了非法的value
id obj = *value;
if (![obj isKindOfClass:[NSArray class]] ) {
return NO;
}
NSLog(@"kvc 可用 value = %@",*value);
return YES;
}
现在我们取key = mySons,类中没有定义mySons 属性,如果定义了,就会自动生成 getter 和 setter 方法,也会生成_mySons
实例变量,那么就不会调用countOfMySons
和 objectInMySonsAtIndex
方法。
HLPerson *person = [[HLPerson alloc]init];
person.sons = @[@"111",@"222",@"333"];
NSArray *arr = [person valueForKey:@"mySons"];
NSLog(@"arr = %@",arr);
当开始打印这个数组的时候,会调用上面提到的两个方法,也就是取值过程中
所说的代理对象将接受到的所有NSArray消息,都转换为countOf<Key>,objectIn<Key> AtIndex:和<key>AtIndexes:消息的某种组合
打印结果:
2021-01-25 19:06:26.540839+0800 KVC--001[13238:981977] -[HLPerson countOfMySons]
2021-01-25 19:06:26.540962+0800 KVC--001[13238:981977] -[HLPerson countOfMySons]
2021-01-25 19:06:26.541053+0800 KVC--001[13238:981977] mySons = 0
2021-01-25 19:06:26.541162+0800 KVC--001[13238:981977] mySons = 1
2021-01-25 19:06:26.541274+0800 KVC--001[13238:981977] mySons = 2
2021-01-25 19:06:26.541414+0800 KVC--001[13238:981977] arr = (
111,
222,
333
)
异常机制
- 防止设置nil 而崩溃
//异常处理
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"%@的值不能为空",key);
}
- 要设置的key 找不到
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"%s",__func__);
}
- 读取的key 找不到
/**
如果以上步骤都没有实现,那么会调用以下方法,表示没有找到对应的key
*/
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"没有找到%@",key);
return key;
}
- 验证键值是否可用
1、可以在类里直接实现实例方法:如下1,系统不会自动验证,需要手动验证,验证通过再设置对应的key 和value
2、类中实现 实例方法2.其实方法1的本质会先查询方法2是否返回YES,如果返回YES就继续设置值,否则不能。
3、 代码3,是应用
1、
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError {
}
2、
//- validateValue:forKey:error: 验证kvc 是否可用时,会调用下面的方法
- (BOOL)validateMySons:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ //在implementation里面加这个方法,它会验证是否设了非法的value
id obj = *value;
if (![obj isKindOfClass:[NSArray class]] ) {
return NO;
}
NSLog(@"kvc 可用 value = %@",*value);
return YES;
}
3、
{
NSArray *normalArr = @[@"dfd",@"sdf",@"ewe",@"dfs"];
NSError *error;
// id value;
// 提前验证一下 是否键值匹配
BOOL result = [person validateValue:&normalArr forKey:@"mySons" error:&error];
if (result) {
[person setValue:normalArr forKey:@"mySons"];
NSArray *arr = [person valueForKey:@"mySons"];
NSLog(@"arr = %@",arr);
}
}
自定义KVC
创建NSObject的分类,添加 自定义 lg_setValue:forKey:
和lg_valueForKey
下面是分别的实现过程,就不一行行分析了,代码中有详细注释
//模拟系统 setValue:forKey:方法
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key {
// 源码不开源,猜测,是不安全的,用于分析过程
if (key == nil || !key.length) {
return;
}
// key 首字母大写
NSString * Key = [key capitalizedString];
// 寻找方法 如果实现了 按次序调用
NSString *fun1 = [NSString stringWithFormat:@"set%@",Key];
NSString *fun2 = [NSString stringWithFormat:@"_set%@",Key];
if ([self performSelectorWithMethodName:fun1 value:value]) {
NSLog(@"*********%@**********",fun1);
return;
} else if ([self performSelectorWithMethodName:fun2 value:value]) {
NSLog(@"*********%@**********",fun2);
return;
}
// 如果上述方法没有实现 要看accessInstanceVariablesDirectly 是否返回YES
// 返回No,抛出异常,为YES就继续寻找 key
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];
}
// 找到对象的所有实例变量
NSMutableArray *mArray = [self getIvarListName];
// 创建要寻找的实例变量
NSString *_key = [NSString stringWithFormat:@"_%@",Key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
//拿到当前的变量
Ivar var = class_getInstanceVariable(self.class, _key.UTF8String);
//给变量设置值
object_setIvar(self, var, value);
return;
} else if ([mArray containsObject:_isKey]) {
Ivar var = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self, var, value);
return;
} else if ([mArray containsObject:key]) {
Ivar var = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self, var, value);
return;
} else if ([mArray containsObject:isKey]) {
Ivar var = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self, var, value);
return;
}
// 若最终还没有找到 就抛出异常
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
setter 中用到的相关方法:
- (BOOL)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;
}
//获取所有的成员变量
- (NSMutableArray *)getIvarListName {
NSMutableArray *mArray = [NSMutableArray array];
unsigned int count;
Ivar *varList = class_copyIvarList(self.class, &count);
for (int i = 0; i< count; i++) {
Ivar var = varList[i];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithUTF8String:varName];
[mArray addObject:name];
}
free(varList);
return mArray;
}
lg_valueForKey
- (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 @"";
}
集合操作符
创建一个HLSon类
@interface HLSon : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *nick;
@property(nonatomic, copy) NSString *subject;
@property(nonatomic, assign) NSInteger age;
@property(nonatomic, assign) int length;
@end
@implementation HLSon
- (void)setName:(NSString *)name {
_name = name;
// NSLog(@"_name = %@",name);
}
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"%@ 不能为空",key);
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"没有找到%@",key);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"不存在%@",key);
return key;
}
@end
列举几个常用的操作符:
@avg 平均数,@count 数量;@sum求和 ;@max最大值;@min最小值
NSMutableArray *sonArray = [NSMutableArray array];
for (int i = 0; i< 6; i++) {
HLSon *son = [[HLSon alloc]init];
NSDictionary* dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[son setValuesForKeysWithDictionary:dict];
[sonArray addObject:son];
}
NSLog(@"%@",[sonArray valueForKeyPath:@"name"]);
// 平均身高
float avg = [[sonArray valueForKeyPath:@"@avg.length"] floatValue];
NSLog(@"avg = %lf",avg);
// 数量
int count = [[sonArray valueForKeyPath:@"@count.length"] intValue];
NSLog(@"count = %d",count);
// 求和
int sum = [[sonArray valueForKeyPath:@"@sum.length"] intValue];
NSLog(@"sum = %d",sum);
count = [[sonArray valueForKeyPath:@"@count"] intValue];
NSLog(@"count = %d",count);
// 求最大值
int max = [[sonArray valueForKeyPath:@"@max.length"] intValue];
NSLog(@"max = %d",max);
int min = [[sonArray valueForKeyPath:@"@min.length"] intValue];
NSLog(@"min = %d",min);
2021-01-25 21:29:51.680847+0800 KVC--001[14057:1053602] avg = 180.000000
2021-01-25 21:29:51.681012+0800 KVC--001[14057:1053602] count = 6
2021-01-25 21:29:51.681205+0800 KVC--001[14057:1053602] sum = 1080
2021-01-25 21:29:51.681421+0800 KVC--001[14057:1053602] count = 6
2021-01-25 21:29:51.681597+0800 KVC--001[14057:1053602] max = 185
2021-01-25 21:29:51.681725+0800 KVC--001[14057:1053602] min = 175
@unionOfObjects, 返回所有的操作对象指定的属性的集合
@distinctUnionOfObjects,返回所有的操作对象指定的属性的集合,去重
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0;i<6; i++) {
HLSon *son = [[HLSon alloc]init];
NSDictionary *dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[son setValuesForKeysWithDictionary:dict];
[personArray addObject:son];
}
NSLog(@"%@",[personArray valueForKey:@"length"]);
// 返回操作对象指定的属性值集合
NSArray *arr1 = [personArray valueForKeyPath:@"@unionOfObjects.length"];
NSLog(@"arr1 = %@",arr1);
// 返回操作对象指定的属性值集合 去重
NSArray *arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.length"];
NSLog(@"arr2 = %@",arr2);
2021-01-25 21:32:49.539739+0800 KVC--001[14100:1055907] (
175,
185,
175,
179,
177,
175
)
2021-01-25 21:32:49.539970+0800 KVC--001[14100:1055907] arr1 = (
175,
185,
175,
179,
177,
175
)
2021-01-25 21:32:49.540155+0800 KVC--001[14100:1055907] arr2 = (
175,
185,
177,
179
)
嵌套集合操作
@distinctUnionOfArrays @unionArrays @distinctUnionOfSets
操作对象是嵌套在内层数组中的 对象
@distinctUnionOfArrays
,对所有数组中的对象的指定属性值的集合 求并集 去重
@unionArrays
,对所有数组中的对象 的 指定属性值 的集合 求并集
@distinctUnionOfSets
,同理,针对的是 NSSet;NSMutableSet;NSOrderSet,NSMutableOrderSet
NSMutableArray *sonArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i ++ ) {
HLSon *son = [[HLSon alloc]init];
NSDictionary *dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[son setValuesForKeysWithDictionary:dict];
[sonArray1 addObject:son];
}
NSMutableArray *sonArray2 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
HLSon *son = [[HLSon alloc]init];
NSDictionary *dict = @{
@"name":@"Tom",
@"age":@(18+i),
@"nick":@"Cat",
@"length":@(175 + 2*arc4random_uniform(6)),
};
[son setValuesForKeysWithDictionary:dict];
[sonArray2 addObject:son];
}
NSArray *arr1 = [sonArray1 valueForKeyPath:@"@unionOfObjects.length"];
NSLog(@"arr1 = %@",arr1);
NSArray *arr2 = [sonArray2 valueForKeyPath:@"@unionOfObjects.length"];
NSLog(@"arr2 = %@",arr2);
NSArray *nestArray = @[sonArray1,sonArray2];
// 求操作对象属性值的集合 的并集 去重
NSArray *arr = [nestArray valueForKeyPath:@"@distinctUnionOfArrays.length"];
NSLog(@"arr = %@",arr);
// 求操作对象属性值的集合的并集
NSArray *arr3 = [nestArray valueForKeyPath:@"@unionOfArrays.length"];
NSLog(@"arr3 = %@",arr3);
2021-01-25 21:42:05.396213+0800 KVC--001[14184:1060832] arr1 = (
185,
185,
177,
179,
185,
175
)
2021-01-25 21:42:05.396487+0800 KVC--001[14184:1060832] arr2 = (
175,
181,
181,
175,
185,
185
)
2021-01-25 21:42:05.396756+0800 KVC--001[14184:1060832] arr = (
185,
181,
179,
177,
175
)
2021-01-25 21:42:05.396982+0800 KVC--001[14184:1060832] arr3 = (
185,
185,
177,
179,
185,
175,
175,
181,
181,
175,
185,
185
)
KVC的好处
- 使用更简洁的代码访问对象的属性,仅仅用一个 字符串key 就可以
- 可以访问和修改 对象的 私有属性和变量
- 在多值操作中,model 和 字典可以相互转换,效率更快
- 对集合使用kvc,可以对集合中的每个对象的属性进行操作
网友评论