首先在开始之前介绍一个常见的术语:
一. 成员变量、实例变量、属性
- 成员变量:@interface() 生命的全部都是成员变量
- 实例变量: 实例变量是一种特殊的成员变量,由实例创建的对象 class类实例化出来的对象就是实例对象、id是一种特殊的class
-
属性 :@property声明的都是属性
propertyy.png
苹果最早的编译器是GCC 目前升级为LLVM,它的优点是:
没有匹配到实例变量的属性时,会自动创建一个带下划线的属性,_name
@property
根据了解,@property标签主要就是用来帮大家生成get\set方法还能管理内存,但是在xcode4.4之前是需要配合一个叫@synthsize的标签才能合成set\get方法,这么说,也就是说看上去我们只写了一个@property其实系统还是默认加上了@synthsize。
@synthsize
是在编译期间系统会检查,用户是否实现了set\get方法如果实现了,就使用用户自己实现的,如果没有系统会默认加上.
@sythesie namep = _namep;指定属性对应的实例变量.
@dynamic
经查阅这个标签是告诉编译器,大爷我自己实现set/get方法,不管我到底实现没别来烦我(提示警告),如果依然没实现get\set而你又调用了该属性,那么结果就是程序崩溃.
二. KVC原理探索(分析源码)
推荐一个好的分析工具 苹果的官方文件
- KVC 是什么
KVC是一种机制,通过NSKeyValueCoding 这种协议间接来访问他们的成员变量 ,通过键值编码的方式 (字典key-value). - KVC有什么作用
键值编码,属性的间接访问,赋值..
[p setValue:@"hello" forKey:@"namep"];//key
[self.textFiled setValue:[UIColor orangeColor] forKeyPath:@"_placeholderLabel.textColor"];//路由的方式
需要注意的是:如果是通过路由的方式访问一定要使用KeyPath的方式否则会奔溃.
- KVC的原理是什么
- 赋值&取值的过程
赋值过程:
1• 先找相关方法:通过set<Key>,_set <key>,setis<key> 的顺序来取值 ,对于非对象属性,将value的未包装版本设置为value。
2• 若没有相关方法 +(BOOL)accessInstanceVariablesDriectly,判断是否可以间接访问成员变量
• 如果是NO,直接执行KVC的setValue:forUndefineKey:(系统会跑出一个异常,未定义key)
• 如果是YES,继续查找相关变量 _<key>,_is<Key>,<Key>,is<Key>
3• 如果成员变量都不存在,setValue:forUndefineKey 抛出异常.
setter.png
Person *p = [[Person alloc] init];
[p setValue:@"hello" forKey:@"name"];
NSLog(@"%@",p->isName);//(null)
NSLog(@"%@-%@",p->name,p->isName);//(null)-(null)
NSLog(@"%@-%@-%@",p->name,p->isName,p->_isName);//(null)-(null)-(null)
NSLog(@"%@-%@-%@-%@",p->name,p->isName,p->_name,p->_isName);//(null)-(null)-hello-(null)
取值过程:
• 先找相关方法 :(分为两种类型: 简单的类型&集合类型)
1• 简单类型Basic Setter 在实例中搜索第一个访问器方法,该方法的名称是 get<Key>, <key>, is<Key>, or _<key>方法有没有实现,如果找到:
如果检索到的属性值是基础数据类型,则只需返回结果。
如果该值是由NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回该值。
如果结果是不被NSNumber支持的标量类型,转换成NSValue对象并返回它。 否则下一步
2• 集合类型:通过countOf<Key> & objectIn<Key>AtIndex: (来源于NSArray和NSSet)
如果没有找到简单的访问方法,则在实例中搜索名称countOf<Key> & objectIn<Key>AtIndex:(对应于NSArray类定义的基本方法)和 AtIndexes:(对应于NSArray方法objectsAtIndexes:)匹配的方法。
如果找到其中一个方法那么创建一个集合代理对象来响应所有NSArray方法并返回它,否则下一步.
3• 如果没有找到简单的访问方法或数组访问方法组,则查找名为countOf、enumeratorOf和memberOf:(对应于NSSet类定义的基本方法)的三种方法。
4• 若没有找到相关方法 +(BOOL)accessInstanceVariablesDriectly,判断是否可以间接访问成员变量,默认返回YES。YES就会访问下层一系列的 _<key>, _is<Key>, <key>, or is<Key>
• 如果是NO,直接执行KVC的setValue:forUndefineKey:(系统会抛出一个异常,未定义key)
• 如果是YES,继续查找相关变量 _<key>,_is<Key>,<Key>,is<Key>
5• 如果成员变量都不存在,setValue:forUndefineKey 抛出异常.
path.png
Person *p = [[Person alloc] init];
[p setValue:@"name" forKey:@"name"];
p->_name = @"_name";
p->_isName = @"_isName";
p->name = @"name";
p->isName = @"isName";
NSLog(@"%@",[p valueForKey:@"name"]); // 方法 getName 输出: _name 第三步
三. 自定义KVC
仿照objc的源码的思路
- (void)customer_setValue:(nullable id)value forKey:(NSString *)key{
// 1:刷选key 判断非空
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 performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self 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]) {
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;
}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)customer_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)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;
}
- (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;
}
网友评论