和谐学习!不急不躁!!我是你们的老朋友小青龙~
承接文章:OC之Method_Swizling一些坑点、KVC原理分析
前言
上篇文章,我们对KVC的原理进行了分析,本文我将和大家一起自定义实现一个独属于我们自己的KVC
。我仿佛已经听到了你们的呐喊:赶紧嘚,别墨迹~
正文
炒冷饭:
// Setter原理:
1. 依次查找`set<Key>:` 或 `_set<Key>`,找到了就调用方法;找不到就进入步骤2;
2、先确定AccessInstanceVariables返回YES,然后依次查找`_<key>`, `_is<Key>`, `<key>`, 或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
(比如找到了查找`_<key>`,那么后面的`_is<Key>`等就不需要找了)。
3、调用`setValue:forUndefinedKey:`并引发`异常`。
// Getter原理:
1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤2;
2、在实例方法中搜索:countOf<Key>和objectIn<Key>AtIndex和<key>AtIndexes:,
countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤3;
3、改为搜索countOf<Key>、Enumeratorf<Key>和memberOf<Key>:,3个方法都存在才行,否则就进入步骤4;
4、当确定AccessInstanceVariables方法返回YES(默认也是YES),
顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
如果找到,直接获取实例变量的值并继续执行步骤5。否则,继续执行步骤6。
5、如果检索到的属性值是对象指针,只需返回结果。
如果该值是NSNumber支持的标量类型,请将其存储在NSNumber实例中并返回该值。
如果结果是NSNumber不支持的标量类型,请转换为NSValue对象并返回该对象。
6、如果都找不到,调用valueForUndefinedKey:并抛出异常。
参考以上原理,我们给NSObject
添加一个NSObject+SSJKVC分类
:
// NSObject+SSJKVC.h
@interface NSObject (SSJKVC)
- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key;
@end
NSObject+SSJKVC.m
实现部分,我们可以根据前面原理整体得到如下伪代码:
// NSObject+SSJKVC.m
#import "NSObject+SSJKVC.h"
#import <objc/runtime.h>
@implementation NSObject (SSJKVC)
#pragma mark -- Setter
- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key{
/***
-> key 非空判断
步骤1、 依次查找`set<Key>:` 或 `_set<Key>`,找到了就调用方法;找不到就进入步骤2;
步骤2、先确定AccessInstanceVariables返回YES,
然后从实例变量列表依次查找`_<key>`, `_is<Key>`, `<key>`,
或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
(比如找到了`_<key>`,那么后面的`_is<Key>`等就不需要找了)。
步骤3、抛出异常`。
*/
}
#pragma mark -- getter
- (nullable id)valueForKey:(NSString *)key{
/***
-> key 非空判断
步骤1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤2;
步骤2、在实例中搜索:countOf<Key>和objectIn<Key>AtIndex和<Key>AtIndexes:,
countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤3;
步骤3、判断能否给实例变量赋值,不能就抛出异常,能就进入步骤4;
步骤4、
顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
取出对应实例变量的值并返回,否则返回空字符串
*/
}
ssj_setValue具体实现:
- (void)ssj_setValue:(nullable id)value forKey:(NSString *)key{
/***
-> key 非空判断
步骤1、 依次查找`set<Key>:` 或 `_set<Key>`,找到了就调用方法;找不到就进入步骤2;
步骤2、先确定AccessInstanceVariables返回YES,然后从实例变量列表依次查找`_<key>`, `_is<Key>`, `<key>`, 或`is<Key>`,找到了就用输入值设置变量。找不到就进入步骤3;
(比如找到了`_<key>`,那么后面的`_is<Key>`等就不需要找了)。
步骤3、抛出异常`。
*/
NSLog(@"欢迎使用NSObject+SSJKVC");
if (key) {
NSString *bigDealWithKey = [self smallToBigWithFirstCharFrom:key];//首字母大写
// ***************************** 步骤1 ****************************
NSString *selNameOne = [NSString stringWithFormat:@"set%@:",bigDealWithKey];
NSString *selNameTwo = [NSString stringWithFormat:@"_set%@:",bigDealWithKey];
BOOL realizationSelOne= [self respondsToSelector:NSSelectorFromString(selNameOne)];
BOOL realizationSelTwo= [self respondsToSelector:NSSelectorFromString(selNameTwo)];
/// 去掉调用performSelector产生的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
/// 查找`set<Key>:`
if (realizationSelOne) {
[self performSelector:NSSelectorFromString(selNameOne) withObject:value];
/// 找到这个方法,后面的不需要执行
return;
}
/// 查找`_set<Key>`
if (realizationSelTwo) {
[self performSelector:NSSelectorFromString(selNameTwo) withObject:value];
/// 找到这个方法,后面的不需要执行
return;
}
// ***************************** 步骤2 ****************************
/// 先确定AccessInstanceVariables返回YES
if ([self.class accessInstanceVariablesDirectly]) {
//依次查找`_<key>`, `_is<Key>`, `<key>`或`is<Key>`
NSString *_key = [@"_" stringByAppendingString:key];
NSString *_isKey = [@"_is" stringByAppendingString:bigDealWithKey];
NSString *isKey = [@"is" stringByAppendingString:bigDealWithKey];
NSMutableArray *ar = [self getIvarListName];//得到所有实例变量
if ([ar containsObject:_key]) {//_key与ar匹配
[self ssj_changeIvar:_key value:value]; //给实例变量赋值
return;
}
if ([ar containsObject:_isKey]) {
[self ssj_changeIvar:_isKey value:value];
return;
}
if ([ar containsObject:key]) {
[self ssj_changeIvar:key value:value];
return;
}
if ([ar containsObject:isKey]) {
[self ssj_changeIvar:isKey value:value];
return;
}
}
}
// ***************************** 步骤3 ****************************
/// 抛出异常
[self throwSetterException];
}
重写valueForKey
- (nullable id)valueForKey:(NSString *)key{
/***
-> key 非空判断
步骤1、查找 get<Key>, <key>, is<Key>, or _<key>,找到了就进入步骤5;找不到就进入步骤2;
步骤2、在实例中搜索:countOf<Key>和objectIn<Key>AtIndex和<Key>AtIndexes:,
countOf<Key>必须实现,另外两个找到了其中一个,就创建集合代理对象,找不到就进入步骤3;
步骤3、判断能否给实例变量赋值,不能就抛出异常,能就进入步骤4;
步骤4、
顺序搜索名为 _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
取出对应实例变量的值并返回,否则返回空字符串
*/
if (key) {
NSString *bigDealWithKey = [self smallToBigWithFirstCharFrom:key];//首字母大写
// ***************************** 步骤1 ****************************
NSString *getKey = [NSString stringWithFormat:@"get%@",bigDealWithKey];
NSString *_key = [NSString stringWithFormat:@"_%@",key];
BOOL realization_getKey= [self respondsToSelector:NSSelectorFromString(getKey)];
BOOL realization_key= [self respondsToSelector:NSSelectorFromString(key)];
BOOL realization__key= [self respondsToSelector:NSSelectorFromString(_key)];
/// 去掉调用performSelector产生的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
/// 查找`set<Key>:`
if (realization_getKey) {
return [self performSelector:NSSelectorFromString(getKey) withObject:key];
}
if (realization_key) {
return [self performSelector:NSSelectorFromString(key) withObject:key];
}
if (realization__key) {
return [self performSelector:NSSelectorFromString(_key) withObject:key];
}
// ***************************** 步骤2 ****************************
/// countOf<Key>和objectIn<Key>AtIndex:和<key>AtIndexes:
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",bigDealWithKey];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",bigDealWithKey];
NSString *keyAtIndexes = [NSString stringWithFormat:@"%@AtIndexes:",key];
BOOL realization_countOfkey = [self respondsToSelector:NSSelectorFromString(countOfKey)];
BOOL realization_objectInKeyAtIndex = [self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)];
BOOL realization_keyAtIndexes = [self respondsToSelector:NSSelectorFromString(keyAtIndexes)];
// countOf<Key>必须执行,objectIn<Key>AtIndex:和<key>AtIndexes: 二选一
if (realization_countOfkey) {
if (realization_objectInKeyAtIndex) {
return [self performSelector:NSSelectorFromString(objectInKeyAtIndex)];
}else{
if (realization_keyAtIndexes) {
return [self performSelector:NSSelectorFromString(keyAtIndexes)];
}
}
}
// ***************************** 步骤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 ****************************
/// _<key>, _is<Key>, <key>, 或is<Key>的实例变量。
NSMutableArray *ar = [self getIvarListName];//得到所有实例变量
NSString *isKey = [NSString stringWithFormat:@"is%@",bigDealWithKey];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",bigDealWithKey];
if ([ar containsObject:_key]) {//_key与ar匹配
NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:_key]);
return [self ssj_IvarValue:_key]; //给实例变量赋值
}else if ([ar containsObject:_isKey]) {
NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:_isKey]);
return [self ssj_IvarValue:_isKey];
}else if ([ar containsObject:key]) {
NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:key]);
return [self ssj_IvarValue:key];
}else if ([ar containsObject:isKey]) {
NSLog(@"ssj_IvarValue---%@",[self ssj_IvarValue:isKey]);
return [self ssj_IvarValue:isKey];
}
}
return @"";
}
其它公共方法
- (void)throwSetterException{
NSLog(@"setter 抛出异常");
@throw [NSException exceptionWithName:@"SSJ_UnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
/// 将参数string第一个字母转化为大写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
- (NSString *)smallToBigWithFirstCharFrom:(NSString *)string{
if (string) {
if (string.length == 1) {//如果长度为1
return string.uppercaseString;
}else if (string.length > 1) {
// 获取第一个转化为大写字母,然后拼接剩下的内容,然后return
NSString *firstChar = [string substringToIndex:1];
NSString *atferDeal = firstChar.uppercaseString;
NSString *endStr = [string substringFromIndex:1];
return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
}else{
return @"";
}
}
return nil;
}
/// 给实例变量赋值
- (void)ssj_changeIvar:(NSString *)key value:(NSString *)value{
// 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
// 对相应的 ivar 设置值
object_setIvar(self , ivar, value);
}
/// 获取实例变量的值
- (id)ssj_IvarValue:(NSString *)key{
// 获取相应的 ivar
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
// 对相应的 ivar 设置值
return object_getIvar(self, ivar);
}
/// 获取所有实例变量,返回数组
- (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;
}
测试
// SSJPerson
@interface SSJPerson : NSObject{
@public
NSString *boddy;
NSString *_boddy;
NSString *isBoddy;
NSString *_isBoddy;
}
@end
@implementation SSJPerson
@end
运行:
代码
百度网盘
链接:pan.baidu.com/s/1W0XleXhK…
密码:82ur
废话连篇
每一次的探索都是一次成长。
只有通过不断的学习,才能让我们变得越来越优秀。
欢迎大家在评论区留言~