一、定義
KVC(Key-value coding)鍵值編碼,對NSObjcet的擴(kuò)展土涝,開發(fā)者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值濒憋。而不需要調(diào)用明確的存取方法何暇。
二、主要使用場景
1.動(dòng)態(tài)地設(shè)值和取值
可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問和修改對象的屬性凛驮;例如進(jìn)行json轉(zhuǎn)model操作時(shí)裆站,可以使用runtime來獲取成員變量,并利用KVC進(jìn)行修改黔夭。
2.訪問和修改私有變量
當(dāng)我們需要訪問和修改.m文件中聲明私有屬性宏胯,KVC是一大利器;例如更改某些三方庫或者系統(tǒng)的私有變量本姥。
三肩袍、調(diào)用順序
對于平時(shí)開發(fā)來說,調(diào)用順序我們并不需要太多了解婚惫,為了更好的理解實(shí)現(xiàn)原理氛赐,這里進(jìn)行簡單梳理
新建Person
@interface Person : NSObject
{
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation Person
-(instancetype)init {
if (self = [super init]) {
_name = @"這是_name";
_isName = @"這是_isName";
name = @"這是name";
isName = @"這是isName";
}
return self;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSString *name = [person valueForKey:@"name"];
NSLog(@"%@",name);
}
打印結(jié)果
2020-04-13 17:12:27.622399+0800 Algorithm[18089:316164] 這是_name
1
這個(gè)時(shí)候假設(shè)沒有"_name"屬性
@interface Person : NSObject
{
// NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
@implementation Person
-(instancetype)init {
if (self = [super init]) {
// _name = @"這是_name";
_isName = @"這是_isName";
name = @"這是name";
isName = @"這是isName";
}
return self;
}
@end
打印結(jié)果
2020-04-13 17:19:57.662839+0800 Algorithm[18327:321939] 這是_isName
由此依次操作,可查找一個(gè)命名規(guī)則為_name先舷、_isName艰管、name、isName的實(shí)例變量密浑。根據(jù)這個(gè)順序蛙婴,如果發(fā)現(xiàn)則獲取實(shí)例變量值。
你以為這樣就結(jié)束了尔破,不可能的街图,這個(gè)時(shí)候來一個(gè)騷操作
// Person.m
// 是否允許讀取實(shí)例變量的值,如果為YES則在KVC查找的過程中懒构,從內(nèi)存中讀取屬性餐济、實(shí)例變量的值。
// 如果不允許外界通過KVC對我們的私有屬性和成員變量進(jìn)行操作胆剧,則可以設(shè)置此值為NO絮姆。
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
這個(gè)時(shí)候就崩潰了,可以看出不能進(jìn)行訪問屬性
2020-04-14 13:35:21.245193+0800 Algorithm[10324:177961] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x600003fc9320> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
再來一個(gè)騷操作
// Person.m
-(NSString *)getName {
return @"這是方法getName";
}
-(NSString *)name {
return @"這是方法name";
}
-(NSString *)isName {
return @"這是方法isName";
}
加入上面方法后又正常了秩霍,完整的調(diào)用順序如下:
1篙悯、查找是否實(shí)現(xiàn)getter方法,依次匹配-get<Key> 和 -<key> 和 -is<Key>和-_<key>铃绒,如果找到鸽照,直接返回。
2颠悬、當(dāng)沒有找到getter方法矮燎,調(diào)用accessInstanceVariablesDirectly詢問
如果返回YES定血, _<key> ,_is<Key>,<key>,is<Key>诞外,找到了返回對應(yīng)的值
如果返回NO澜沟,結(jié)束查找。并調(diào)用 valueForUndefinedKey: 報(bào)異常
3峡谊、如果沒找到getter方法和屬性值茫虽,調(diào)用 valueForUndefinedKey: 報(bào)異常
四、實(shí)現(xiàn)原理
結(jié)合demo靖苇,了解實(shí)現(xiàn)原理(感覺蘋果的底層原理大多都是和runtime有關(guān)席噩。。贤壁。這個(gè)也不例外)直接上代碼
// NSObject+MyKVC.h
@interface NSObject (MyKVC)
-(void)my_setValue:(id)value forKey:(NSString*)key;
-(id)my_valueforKey:(NSString*)key;
@end
// NSObject+MyKVC.m
#import "NSObject+MyKVC.h"
#import <objc/runtime.h>
@implementation NSObject (MyKVC)
-(void)my_setValue:(id)value forKey:(NSString *)key {
if (key == nil || key.length == 0) {
return;
}
if (value == nil) {
[self setNilValueForKey:key];
return;
}
if (![value isKindOfClass:[NSObject class]]) {
@throw @"必須是NSObject類型";
return;
}
NSString *setter = [[@"set" stringByAppendingString:[key capitalizedString]] stringByAppendingString:@":"];
if ([self respondsToSelector:NSSelectorFromString(setter)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(setter) withObject:value];
#pragma clang diagnostic pop
}else{
// 獲取屬性列表
unsigned int count;
BOOL flag = false;
Ivar* vars = class_copyIvarList([self class], &count);
for (NSInteger i = 0; i<count; i++) {
Ivar var = vars[I];
NSString* keyName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
// 按順序匹配到就結(jié)束
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",key.capitalizedString]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:key]) {
flag = true;
object_setIvar(self, var, value);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",key.capitalizedString]]) {
flag = true;
object_setIvar(self, var, value);
break;
}
}
// 沒有找到
if (!flag) {
[self setValue:value forUndefinedKey:key];
}
}
}
-(id)my_valueforKey:(NSString *)key{
if (key == nil || key.length == 0) {
return nil;
}
//查找getter方法
NSString* getFuncName = [NSString stringWithFormat:@"get%@",key.capitalizedString];
NSString* isFuncName = [NSString stringWithFormat:@"is%@",key.capitalizedString];
// 查找 -get<Key>
if ([self respondsToSelector:NSSelectorFromString(getFuncName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(getFuncName)];
#pragma clang diagnostic pop
} else if ([self respondsToSelector:NSSelectorFromString(key)]) { // 查找 -<key>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(key)];
#pragma clang diagnostic pop
} else if ([self respondsToSelector:NSSelectorFromString(isFuncName)]) { // 查找 is<Key>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(isFuncName)];
#pragma clang diagnostic pop
} else { // 詢問 accessInstanceVariablesDirectly悼枢,是否繼續(xù)查找屬性,默認(rèn)返回YES
// 先看順序:_<key>, _is<Key>, <key>, is<Key>
// 獲取屬性列表
unsigned int count;
BOOL flag = false;
Ivar* vars = class_copyIvarList([self class], &count);
for (NSInteger i = 0; i<count; i++) {
Ivar var = vars[I];
NSString* keyName = [NSString stringWithCString:ivar_getName(var) encoding:NSUTF8StringEncoding];
// 按順序匹配到就結(jié)束
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@",key]]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@",key.capitalizedString]]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:key]) {
flag = true;
return object_getIvar(self, var);
break;
}
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@",key.capitalizedString]]) {
flag = true;
return object_getIvar(self, var);
break;
}
}
// 沒有找到
if (!flag) {
[self valueForUndefinedKey:key];
}
return nil;
}
return nil;
}
@end
調(diào)用代碼
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSString *name = [person my_valueforKey:@"name"];
NSLog(@"%@",name);
}
原創(chuàng)出處:https://blog.csdn.net/qq_32644987/article/details/105489083