前言

开发过程中,很多人都会注意到KVO,以及自定义KVO,实际上KVC的作用也是十分强大的,不仅仅是简单的字典转模型,有关使用技巧可以看上篇文章,这篇文章要根据上篇的总结来进行自定义KVC操作;

相关代码:KVCCode(上篇代码也在这里)

KVC原理

实际在自定义过程中主要要注意的2大点:1.KVC设置过程,2.KVC取值过程,

1.KVC赋值过程

1:非空判断一下

2:找到相关方法set<Key>,_set<Key>,_setIs<Key>实例方法进行赋值

3:判断是否能够直接赋值实例变量判断,即accessInstanceVariablesDirectly,且返回值为YES;

3.1:找相关实例变量进行赋值

3.1.1 定义一个收集实例变量的可变数组

3.1.2 获取相应的 ivar

3.1.3 对相应的 ivar 设置值

4.如果找不到相关实例setValue:forUndefinedKey报出异常

- (void)xz_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 xz_performSelectorWithMethodName:setKey value:value]) {NSLog(@"*********%@**********",setKey);return;}else if ([self xz_performSelectorWithMethodName:_setKey value:value]) {NSLog(@"*********%@**********",_setKey);return;}else if ([self xz_performSelectorWithMethodName:setIsKey value:value]) {NSLog(@"*********%@**********",setIsKey);return;}// 3:判断是否能够直接赋值实例变量if (![self.class accessInstanceVariablesDirectly] ) {@throw [NSException exceptionWithName:@"XZUnknownKeyException" 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 获取相应的 ivarIvar 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:@"XZUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
#pragma mark - 方法分发
- (BOOL)xz_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 popreturn 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;
}

2.KVC取值过程(ValueForKey:)

1.对key 判断非空

2.找到相关方法getKey, key, isKey, _key,

3:判断是否能够直接赋值实例变量是否实现类方法accessInstanceVariablesDirectly

4..按照 _key,_iskey,key,isKey 顺序查询实例变量

5. 抛出异常ValueForUndefinedKey 报错

- (nullable id)xz_valueForKey:(NSString *)key{// 1:刷选key 判断非空if (key == nil  || key.length == 0) {return nil;}// 2:找到相关方法 getKey, key, isKey, _key// key 要大写NSString *Key = key.capitalizedString;NSString *getKey = [NSString stringWithFormat:@"get%@:",Key];NSString *isKey = [NSString stringWithFormat:@"is%@:",Key];NSString *_key = [NSString stringWithFormat:@"_%@:",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(isKey)]){return [self performSelector:NSSelectorFromString(isKey)];} else if ([self respondsToSelector:NSSelectorFromString(_key)]){return [self performSelector:NSSelectorFromString(_key)];}
#pragma clang diagnostic pop// 3:判断是否能够直接赋值实例变量if (![self.class accessInstanceVariablesDirectly] ) {@throw [NSException exceptionWithName:@"XZUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];}// 4.按照 _key,_iskey,key,isKey 顺序查询实例变量NSMutableArray *mArray = [self getIvarListName];_key = [NSString stringWithFormat:@"_%@",key];NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];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);;}// 5.抛出异常@throw [NSException exceptionWithName:@"XZUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: valueForUndefinedKey:%@.****",self,NSStringFromSelector(_cmd),key] userInfo:nil];return @"";
}

取值过程的自定义也结束了,其实这里也有不严谨的地方,比如取得属性值返回的时候需要根据属性值类型来判断是否要转换成 NSNumber 或 NSValue,以及对 NSArray 和 NSSet 类型的判断。在KVCCode(中有个写的比较牛逼的KVC代码,有兴趣可以下载下来看看)

KVC异常小技巧

下面代码会使用XZPerson类

NS_ASSUME_NONNULL_BEGINtypedef struct {float x, y, z;
} ThreeFloats;@interface XZPerson : NSObject{@publicNSString *name;NSString *_name;NSString *_isName;NSString *isName;}@property (nonatomic, copy) NSString *subject;
@property (nonatomic, assign) int  age;
@property (nonatomic, assign) BOOL sex;
@property (nonatomic) ThreeFloats  threeFloats;@endNS_ASSUME_NONNULL_END

1: KVC 自动转换类型

看下面代码我们在给age赋值的时候一般情况会不能直接复制int类型,会使用下面方式

    XZPerson *person = [[XZPerson alloc] init];[person setValue:@18 forKey:@"age"];NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber// 上面那个表达 大家应该都会! 但是下面这样操作可以?[person setValue:@"20" forKey:@"age"]; // int - stringNSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber

看一下输出结果:

上面使用@18 输出的是__NSCFNumber(类簇,属于NSNumber的子类) 是可以理解的,但是 @“20”也是__NSCFnumber ,这说明在赋值过程会进行对应的类型转换

同样的类型转换,在结构体中也会出现


XZPerson *person = [[XZPerson alloc] init];[person setValue:@"20" forKey:@"sex"];NSLog(@"%@-%@",[person valueForKey:@"sex"],[[person valueForKey:@"sex"] class]);//__NSCFBooleanThreeFloats floats = {1., 2., 3.};NSValue *value  = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];[person setValue:value forKey:@"threeFloats"];NSLog(@"%@-%@",[person valueForKey:@"threeFloats"],[[person valueForKey:@"threeFloats"] class]);//NSConcreteValue

这里的输出过为:

bool 类型会转换为__NSCFBoolean(NSCFNumber) ,结构体会转换为NSConcreteValue (NSValue)类型

2: 设置空值(setNilValueForKey实现方法进行容错提示)

我们可以对age,sex进行设置空置


XZPerson *person = [[XZPerson alloc] init];
NSLog(@"******2: 设置空值******");[person setValue:nil forKey:@"age"]; // subject不会走 - 官方注释里面说只对 NSNumber - NSValue

我们可以在person类中实现setNilValueForKey方法进行监控(注:如果没有实现这个方法会导致崩溃),进行容错提示

- (void)setNilValueForKey:(NSString *)key{NSLog(@"你傻不傻: 设置 %@ 是空值",key);
}

在setNilValueForKey方法的注释文档中描述如下,描述了,可以监控到NSNumber,和NSValue ,是监听不到NSString类型的

/* Given that an invocation of -setValue:forKey: would be unable to set the keyed value because the type of the parameter of the corresponding accessor method is an NSNumber scalar type or NSValue structure type but the value is nil, set the keyed value using some other mechanism. The default implementation of this method raises an NSInvalidArgumentException. You can override it to map nil values to something meaningful in the context of your application.
*/

3: 插入找不到的 key(setValue: forUndefinedKey 进行容错)

如果我们给person中插入一个不存在的属性

    NSLog(@"******3: 找不到的 key******");[person setValue:nil forKey:@"Alan"];

这个时候如果直接运行就会报错,找不到这个key,可以添加setValue: forUndefinedKey:进行容错提示

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{NSLog(@"你瞎啊: %@ 没有这个key",key);}

4: 取值时 - 找不到 key(valueForUndefinedKey:)

取值时去一个不包含的属性,进行容错处理

    // 4: 取值时 - 找不到 keyNSLog(@"******4: 取值时 - 找不到 key******");NSLog(@"%@",[person valueForKey:@"Alan"]);

需要person类中添加方法

- (id)valueForUndefinedKey:(NSString *)key{NSLog(@"你瞎啊: %@ 没有这个key - 给你一个其他的吧,别奔溃了!",key);return @"Master 牛逼";
}

5: 键值验证

这个在开发中用的相对来说较少,主要是封装一些库,可能不想让上层了解具体属性是怎么进行操作的,才会有这种操作:具体如下:给person职工插入names属性进行验证,如过有错误就报错,如果没有错误,输出names和subject值

    NSLog(@"******5: 键值验证******");NSError *error;NSString *name = @"Alan";if (![person validateValue:&name forKey:@"names" error:&error]) {NSLog(@"%@",error);}else{NSLog(@"%@",[person valueForKey:@"subject"]);}if (![person validateValue:&name forKey:@"alan" error:&error]) {NSLog(@"%@",error);}else{NSLog(@"%@",[person valueForKey:@"subject"]);}

如果要实现重定向就需要在person类中实现:

代码逻辑为,如果传入的key值为names的话,就传入的值进行修改拼接了字符,并且存储到subject属性中,如果不是names属性就直接抛出错误

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError{if([inKey isEqualToString:@"names"]){[self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:@"subject"];return YES;}*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的属性",inKey,self] code:10088 userInfo:nil];return NO;
}

输出日志信息:

总结

KVC 探索完了,其实我们探索的大部分内容都是基于苹果的官方文档,我们在探索 iOS 底层的时候,文档思维十分重要,有时候说不定在文档的某个角落里就隐藏着追寻的答案。KVC 用起来不难,理解起来也不难,但是这不意味着我们可以轻视它。在 iOS 13 之前,我们可以通过 KVC 去获取和设置系统的私有属性,但从 iOS 13 之后,这种方式被禁用掉了。建议对 KVC 理解还不透彻的读者去多几遍官方文档,相信我,你会有新的收获。最后,我们简单总结一下本文的内容。

  • KVC 是一种 NSKeyValueCoding 隐式协议所提供的机制。
  • KVC 通过 valueForKey:valueForKeyPath: 来取值,不考虑集合类型的话具体的取值过程如下:
    • get<Key>, <key>, is<Key>, _<key> 的顺序查找方法
    • 如果找不到方法,则通过类方法 accessInstanceVariablesDirectly 判断是否能读取成员变量来返回属性值
    • _<key>, _is<Key>, <key>, is<Key> 的顺序查找成员变量
  • KVC 通过 setValueForKey:setValueForKeyPath: 来取值,不考虑集合类型的话具体的设置值过程如下:
    • set<Key>, _set<Key>的顺序查找方法
    • 如果找不到方法,则通过类方法 accessInstanceVariablesDirectly 判断是否能通过成员变量来返回设置值
    • _<key>, _is<Key>, <key>, is<Key> 的顺序查找成员变量

5种异常处理

  • KVC 自动转换类型

  • 设置空值容错

  • 插入找不到的 key容错

  • 取值时 - 找不到 key容错

  • 键值验证重定向

希望对大家有用处,欢迎大家点赞+评论,关注我的CSDN,我会定期做一些技术分享!未完待续。。。

iOS之KVC原理自定义KVC相关推荐

  1. iOS之深入解析KVC的底层原理和自定义KVC的实现

    一.KVC 简介 ① 定义 KVC 是 Key-Value Coding 的简称,中文译义为键值编码. KVC 是指 iOS 的开发中,可以允许开发者通过 Key 名直接访问对象的属性,或者给对象的属 ...

  2. KVC原理分析及应用

    前言: KVC又称键值编码(Key-Value-Coding) ,在iOS开发中是一个比较常见的技术点,相信一般开发人员都会使用KVC,其主要的两个方法无非就是设置值和取值,相信也有不少人写UI喜欢使 ...

  3. IOS SDK详解之KVC

    原创Blog,转载请注明出处 blog.csdn.net/hello_hwc 前言:本文的架构 KVC的定义 KVC的几个场景 希望,通过本文让不了解KVC的同学入门,KVC在IOS开发中是个比较重要 ...

  4. iOS程序启动原理---iOS-Apple苹果官方文档翻译

    本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...

  5. iOS开发多线程篇—自定义NSOperation

    iOS开发多线程篇-自定义NSOperation 一.实现一个简单的tableView显示效果 实现效果展示: 代码示例(使用以前在主控制器中进行业务处理的方式) 1.新建一个项目,让控制器继承自UI ...

  6. iOS开发那些事--自定义单元格实现

    自定义单元格 当苹果公司提供给的单元格样式不能我们的业务需求的时候,我们需要自定义单元格.在iOS 5之前,自定义单元格可以有两种实现方式:代码实现和用xib技术实现.用xib技术实现相对比较简单,创 ...

  7. iOS程序启动原理(上)

    为什么80%的码农都做不了架构师?>>>    iOS程序启动原理 Info.plist 常见设置 建立一个工程后,会在Supporting files文件夹下看到一个"工 ...

  8. Atitit.struts排除url 的设计and 原理 自定义filter 排除特定url

    Atitit.struts排除url 的设计and 原理 自定义filter 排除特定url 1.1. 原理流程1 2. Invoke1 3. StrutsX2 1.1. 原理流程 读取struts配 ...

  9. Android手机teams,在iOS和Android上自定义Microsoft Teams体验的三种最佳方法

    以下是您可以在iOS和Android上自定义Microsoft Teams应用程序以使其成为自己的三种方法 1. 开启黑暗模式,以便iOS和Android上的小组中的消息和其他内容更易于理解和阅读 2 ...

最新文章

  1. CV领域论文常用单词汇总
  2. pandas中dataframe的构造(csv等结构化文件读取,字典读取)以及保存
  3. 什么是信息服务外包?
  4. ffmpeg转码速度控制方法
  5. 项目调试之小工具---文件名替换
  6. lazada选品,东南亚韩潮周边产品爆卖,单日销售额5万美金!
  7. 同时打开多个VC工程
  8. 经常被问到的十个 Java 面试题?你Get了吗?
  9. 两个mapreduce 做topn_hadoop分布式计算MapReduce详细总结
  10. 北京金融局、通州区政府与蚂蚁金服战略合作 共防系统性金融风
  11. 禁止选中页面内容-兼容ie、firefox、chrome
  12. 基于 Electron 做视频会议的两种实现方式
  13. ajax是异步非阻塞,[转帖]再谈IO的异步,同步,阻塞和非阻塞
  14. matlab聚类实验,实验3Matlab聚类分析
  15. win7开启不了Aero
  16. C# 获取硬盘序列号
  17. 李阳疯狂英语助教工作总结
  18. AD(十九)class、设计参数、规则的创建
  19. 浅谈Android自定义View
  20. 用计算机判断函数单调性吗,高中数学函数单调性的判断方法(全)

热门文章

  1. 关于图计算图学习的基础知识概览:前置知识点学习(PGL)[系列一]
  2. dhtml、html、html5、xml、xhtml的区别
  3. android app unlock sim pin,android Sim卡锁定 pin解锁流程学习
  4. JSON必备工具之Json Viewer
  5. ch9200 usb网卡驱动_21包邮的PCMCIA无线网卡开箱+对比测评
  6. 加那些YY主播的微信为何要花钱?
  7. java准确读取word文件页数
  8. 【mud】call_out()函数
  9. 史上最简单的openshift免费空间上传代码教程!没有之一!
  10. 【JAVA】xml文件的读取