本文由快学吧个人写作,以任何形式转载请表明原文出处。
一、KVC的常用方法
1. 基本类型
比如NSInteger、int、float之类的,用KVC的时候要转成NSNumber或者NSString。
2. 集合类型
不可变数组也可以当成可变数组用。
3. 非对象类型
比如结构体之类的。就要转成NSValue,不然没办法存也没办法取。
4. 字典操作
字典转模型,模型转字典
5. 聚合操作符
@avg: 返回操作对象指定属性的平均值
@count: 返回操作对象指定属性的个数
@max: 返回操作对象指定属性的最大值
@min: 返回操作对象指定属性的最小值
@sum: 返回操作对象指定属性值之和
(1). 小写和字符串长度
小写是 : lowercaseString。字符串长度是length。
(2). 聚合操作符举例
(3). 取数组中所有相同的key的value
(4). 数组嵌套
(5). 集合嵌套
6. 对象嵌套
一个对象是另一个对象的属性,利用keyPath路由来设值和取值。
二、自定义KVC
根据上一章的setValue:ForKey:和valueForKey:的总结来完成的思路,但是只针对了简单的基本类型和数组类型,没有处理集合和其他的。思路大概就是这样的思路,写的很简单。全都测试过了,绝对能正常的使用。
1. 创建NSObject分类
.h文件如下 :
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (JDKVC)
// 自定义setValueForKey
- (void)jd_setValue:(nullable id)value forKey:(NSString *)key;
//
- (nullable id)jd_valueForKey:(NSString *)key;
@end
NS_ASSUME_NONNULL_END
2. 自定义
.m文件如下 :
#import "NSObject+JDKVC.h"
#import <objc/runtime.h>
@implementation NSObject (JDKVC)
/**
自定义响应方法,找的到名字是sName的方法就调用,并把参数value传进去,返回YES。
找不到名字是sName的方法,就直接返回NO
*/
- (BOOL)jd_performSelecorWithName:(NSString *)sName value:(id)value
{
SEL mSEL = NSSelectorFromString(sName);
if ([self respondsToSelector:mSEL]) {
//确认不会内存泄漏就不用写下面这些宏
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// 响应了就调用sName的方法,并把参数value传进去
[self performSelector:mSEL withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
/**
获取调用对象的成员变量,并将成员变量的名字的字符串放入到一个数组中
*/
- (NSMutableArray *)jd_getIvarsName
{
// 初始容量是1,超过了直接*2扩容
NSMutableArray *ivarsMArr = [NSMutableArray arrayWithCapacity:1];
// 定义数量,用于循环遍历
unsigned int count = 0;
// 获取调用对象的类的成员变量列表ivarList,成员变量的数量存入count
Ivar *ivars = class_copyIvarList([self class], &count);
// 遍历成员变量类表,并将其名字变成字符串,将字符串加入到数组中
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *ivarNC = ivar_getName(ivar);
NSString *ivarNStr = [NSString stringWithUTF8String:ivarNC];
NSLog(@"%@的成员变量:%@",[self class],ivarNStr);
[ivarsMArr addObject:ivarNStr];
}
//Ivar需要手动释放
free(ivars);
return ivarsMArr;
}
#pragma mark - 自定义setValueForKey
- (void)jd_setValue:(nullable id)value forKey:(NSString *)key
{
// 传入的key不能是空的
if (!key || key.length == 0) {
NSLog(@"传的key是空的");
return;
}
// 第1步:
// 先找3个setter方法:set<Key> _set<Key> setIs<Key>
// 1.1 因为Key是共用的,且首字母皆要大写,所以先设置一个公共的Key,让它的首字母大写,
// 用NSString自带的capitalizedString就可以,这个就是让字符串首字母大写
NSString *big_key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[key substringToIndex:1].capitalizedString];
// 1.2 把三个setter方法的名字先用字符串保存起来,方便使用
NSString *setKey = [NSString stringWithFormat:@"set%@:",big_key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",big_key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",big_key];
// 1.3 判断是否能找到3个setter方法中的任何一个,找到哪个就调用哪个
if ([self jd_performSelecorWithName:setKey value:value]) {
NSLog(@"找到%@方法了",setKey);
// 找到了就不用向下继续了,已经给变量赋值了,直接return
return;
}
else if ([self jd_performSelecorWithName:_setKey value:value])
{
NSLog(@"找到%@方法了",_setKey);
return;
}
else if ([self jd_performSelecorWithName:setIsKey value:value])
{
NSLog(@"找到%@方法了",setIsKey);
return;
}
// 第2步:
// 找不到3个setter方法,查看accessInstanceVariablesDirectly方法是否返回的是YES
if (![self.class accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"JD_CANT_FIND_SETTER" reason:[NSString stringWithFormat:@"%@can't find three setter methods",self] userInfo:nil];
}
// 第3步 :
// accessInstanceVariablesDirectly为YES会走到这里,间接访问成员变量,进行赋值。
// 3.1 先要把调用者的成员变量拿到,放到一个数组中,然后判断成员变量的名字是否有如下格式,按顺序:
// _<Key> _is<Key> <Key> <isKey>
NSMutableArray *ivarArr = [self jd_getIvarsName];
// 3.2 把名字格式先定义好,方便用
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_iskey = [NSString stringWithFormat:@"_is%@",big_key];
NSString *isKey = [NSString stringWithFormat:@"is%@",big_key];
// 3.3 判断成员变量列表中是否有这四种格式的key
// 哪个先有,哪个就被赋值
if ([ivarArr containsObject:_key]) {
NSLog(@"是%@",_key);
// 拿到对应的成员变量
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 给对应的成员变量赋值
object_setIvar(self, ivar, value);
// 赋值到了就return,不用管其他的
return;
}
else if ([ivarArr containsObject:_iskey])
{
NSLog(@"是%@",_iskey);
Ivar ivar = class_getInstanceVariable([self class], _iskey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
else if ([ivarArr containsObject:key])
{
NSLog(@"是%@",key);
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self, ivar, value);
return;
}
else if ([ivarArr containsObject:isKey])
{
NSLog(@"是%@",isKey);
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self, ivar, value);
return;
}
[self jd_setValue:value ForUndefinedKey:key];
}
- (void)jd_setValue:(id)value ForUndefinedKey:(NSString *)key
{
// 如果实现了系统的setValue:forUndefinedKey:,就不崩溃
NSString *undefineMN = @"setValue:forUndefinedKey:";
IMP undefineIMP = class_getMethodImplementation([self class], NSSelectorFromString(undefineMN));
if (undefineIMP) {
NSLog(@"调用系统的");
[self jd_performSelecorWithName:undefineMN value:value];
}
else{
// 如果没实现,就抛出异常
@throw [NSException exceptionWithName:@"JD_CANT_FIND_IVARS" reason:[NSString stringWithFormat:@"%@ can find four ivars",self] userInfo:nil];
}
}
#pragma mark - 自定义valueForKey
- (nullable id)jd_valueForKey:(NSString *)key
{
// 传入的key不可以是空的
if (!key || key.length == 0) {
NSLog(@"key不能是空的");
return nil;
}
// 第1步 :
// 查找getter的四个方法 : get<Key> <Key> is<Key> _<Key>
// 拿到首字母大写的key
NSString *big_key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[key substringToIndex:1].capitalizedString];
NSString *getKey = [NSString stringWithFormat:@"get%@",big_key];
NSString *isKey = [NSString stringWithFormat:@"is%@",big_key];
NSString *_Key = [NSString stringWithFormat:@"_%@",key];
SEL getKeySEL = NSSelectorFromString(getKey);
SEL keySEL = NSSelectorFromString(key);
SEL isKeySEL = NSSelectorFromString(isKey);
SEL _KeySEL = NSSelectorFromString(_Key);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:getKeySEL])
{
return [self performSelector:getKeySEL];
}
else if ([self respondsToSelector:keySEL])
{
return [self performSelector:keySEL];
}
else if ([self respondsToSelector:isKeySEL])
{
return [self performSelector:isKeySEL];
}
else if ([self respondsToSelector:_KeySEL])
{
return [self performSelector:_KeySEL];
}
// 第2步 :
// 数组的处理
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",big_key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",big_key];
SEL countOfKeySEL = NSSelectorFromString(countOfKey);
SEL objectInKeyAtIndexSEL = NSSelectorFromString(objectInKeyAtIndex);
// countOfKeySEL是必须实现的,否则另外两个关于数组的方法没用
// 不实现就直接走到下个流程
if ([self respondsToSelector:countOfKeySEL]) {
// objectInKeyAtIndex对应的是objectAtIndex
// 这是更常使用的,所以这里只判断是否实现了这个,对另一个
// 数组的方法,暂时不做处理,因为不常用
if ([self respondsToSelector:objectInKeyAtIndexSEL]) {
// 定义一个数组,存储遍历出来的数组元素
NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:1];
// 拿到数组元素的数量
int num = (int)[self performSelector:countOfKeySEL];
for (int j = 0; j < num; j++) {
// 这就是数组里面的每个元素
id tem = [self performSelector:objectInKeyAtIndexSEL withObject:[NSString stringWithFormat:@"%d",j]];
[mArr addObject:tem];
}
return mArr;
}
}
#pragma clang diagnostic pop
// accessInstanceVariablesDirectly如果不是YES
if (![self.class accessInstanceVariablesDirectly]) {
@throw [NSException exceptionWithName:@"JD_CANT_FIND_GETTER" reason:[NSString stringWithFormat:@"%@can't find four getter methods",self] userInfo:nil];
}
// 第3步 :
// 直接从成员变量中拿值,_<Key> _is<Key> <Key> is<Key>
NSMutableArray *ivarMutArr = [self jd_getIvarsName];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",big_key];
if ([ivarMutArr containsObject:_Key]) {
Ivar ivar = class_getInstanceVariable([self class], _Key.UTF8String);
return object_getIvar(self, ivar);
}
else if ([ivarMutArr containsObject:_isKey])
{
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);
}
else if ([ivarMutArr containsObject:key])
{
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);
}
else if ([ivarMutArr containsObject:isKey])
{
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);
}
// 没有设置报错,直接返回了一个固定的字符串,报错和上面的赋值一样
return @"啥也没有";
}
@end
三、KVC的一些异常监听
1. 设置空值
当给某个成员变量设置的value是nil的时候,可以通过setNilValueForKey:方法来监听处理。
不过setNilValueForKey:只能监听到NSNumber或者NSValue类型的空值设定。
如下图,JDMan.h和JDMan.m :
VC中用KVC设置JDMan的所有成员变量都为nil :
运行结果 :
setNilValueForKey:只能监听类型为NSNumber或者NSValue的变量设置为nil。
2. UndefinedKey
在JDMan.m中重写它们的实现。可以保证设置的key是未知的时候不崩溃。