本文章属于-mr_young_原创,转载请注明出处:
https://www.jianshu.com/p/23dce2ead05f
1.什么是KVC?
KVC
:即key-value-coding
,键值编码。
2.KVC应用场景
1> 通过键值路径可为对象的属性进行赋值,也可以设置对象的私有属性。
Person *person = [Person alloc] init];
[person setValue:@"young" forKey:@"name"];
// 如果对象的某一属性也是对象
[person setValue:@"doge" forKeyPath:@"dog.name"];
2> 通过键值路径获取对象某一属性的值,也可以获取私有属性。
NSString *pName = [person valueForKey:@"name"];
// 也可以通过kvc获取到dog的属性
NSString *dogName = [person valueForKeyPath:@"dog.name"];
3> 简单的字典-->模型
#import
@class Dog;
@interface Person : NSObject
@property (nonatomic, assign) int p_id
@property (nonatomic, copy) NSString name;
@property (nonatomic, strong) Dog *dog;
+ (instancetype)personWithDict:(NSDictionary *)dict;
@end
#import "Person.h"
#import "Dog.h"
@implementation Person
+ (instancetype)personWithDict:(NSDictionary *)dict {
// dict: @{@"id":@(10), @"name":@"young", @"dog":@{@"name":@"doge", @"age":@(3)}}
Person *person = [Person alloc] init];
[person setValuesForKeysWithDictionary:dict];
return person;
}
// Person类中找不到key(此时找不到id)对应的属性时,调用此方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"id"]) {
self.p_id = [value intValue];
} else {
NSLog(@"Person找不到%@对应的属性", key);
return nil;
}
}
@end
4> 用KVC获取集合中的元素个数、最大值、最小值、平均值以及求和。⚠️注意:通过KVC来获取集合中元素的个数时应当使用@"@count"
。
#import
#import "Person.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc] init];
Dog *dog1 = [[Dog alloc] init];
dog1.name = @"dog1";
dog1.age = 3;
p1.name = @"young";
p1.dog = dog1;
Person *p2 = [[Person alloc] init];
Dog *dog2 = [[Dog alloc] init];
dog2.name = @"dog2";
dog2.age = 2;
p2.name = @"wang";
p2.dog = dog2;
Person *p3 = [[Person alloc] init];
Dog *dog3 = [[Dog alloc] init];
dog3.name = @"dog3";
dog3.age = 6;
p3.name = @"zhang";
p3.dog = dog3;
NSArray *arr = [NSArray arrayWithObjects:p1, p2, p3, nil];
NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@",
[arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.dog.age"],
[arr valueForKeyPath:@"@min.dog.age"], [arr valueForKeyPath:@"@avg.dog.age"],
[arr valueForKeyPath:@"@sum.dog.age"]);
}
return 0;
}
3.手撸KVC之前
3.1 首先,我们要知道在OC中一个属性对应有四个成员变量,并且他们的优先级依次为:_key
>_isKey
>key
>isKey
。
// Person.h
#import
@interface Person : NSObject {
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
/**
NSString *_isName;
NSString *name;
NSString *isName;
*/
/**
NSString *name;
NSString *isName;
*/
/**
NSString *isName;
*/
}
@end
// Person.m
#import "Person.h"
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
// 运行结果: KVC-Test[1799:310739] name: _name
/**
// _name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
// 运行结果: KVC-Test[1799:310739] name: _isName
// _name = @"_name";
// _isName = @"_isName";
name = @"name";
isName = @"isName";
// 运行结果: KVC-Test[1799:310739] name: name
// _name = @"_name";
// _isName = @"_isName";
// name = @"name";
isName = @"isName";
// 运行结果: KVC-Test[1799:310739] name: isName
*/
}
return self;
}
@end
// main.m
#import
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"name: %@", [person valueForKey:@"name"]);
}
return 0;
}
@end
3.2 通过valueForKey:@"key"
获取对象某一属性时:
- 首先,调用getter方法,一个属性对应有3个getter方法,并且它们的优先级依次为:
getKey
>key
>isKey
。
// 还是用3.1的例子,重写Person类的3个getter方法
// Person.m
#import "Person.h"
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
}
return self;
}
- (NSString *)getName {
// 运行结果:KVC-Test[2115:403089] name: getter- getName
return @"getter- getName";
}
- (NSString *)name {
// 注释getName方法后,运行结果:KVC-Test[2115:403089] name: getter - name
return @"getter - name";
}
- (NSString *)isName {
// 注释getName和name方法后,运行结果:KVC-Test[2115:403089] name: getter - isName
return @"getter - isName";
}
@end
- 如果没有getter方法,则会调用NSArry的两个对象方法
- (NSInteger)countOfKey
和- (id)objectInKeyAtIndex:(NSInteger)index
,如果实现了这两个方法则会返回一个数组。
#import "Person.h"
@implementation Person
- (NSUInteger)countOfName {
return 10;
}
- (id)objectInNameAtIndex:(NSUInteger)index {
return [NSString stringWithFormat:@"name-%lu", (unsigned long)index];
}
// 打印结果为
//KVC-Test[2278:424945] name: (
// "name-0",
// "name-1",
// "name-2",
// "name-3",
// "name-4",
// "name-5",
// "name-6",
// "name-7",
// "name-8",
// "name-9"
// )
//Program ended with exit code: 0
@end
3.2.2运行结果
- 如果没有NSArray的两个对象方法,则会调用类方法
+(BOOL)accessInstanceVariablesDirectly
。返回NO,则不会查找任何成员变量,并且报错valueForUndefinedKey:]: this class is not key value coding-compliant for the key
。因此,该类方法默认返回YES。
- 只有
+ (BOOL)accessInstanceVariablesDirectly
返回YES时,KVC才会查找Person
类中的4个成员变量:_key
、_isKey
、key
、isKey
(_name
、_isName
、name
、isName
)。它们优先级依次为_key
>_isKey
>key
>isKey
, 当找到_key
时,即返回_key
的值,如果找不到_key
再查找_isKey
...如果最后找不到isKey
的成员变量时,同样报错valueForUndefinedKey:]: this class is not key value coding-compliant for the key
。
- 如果类方法
+ (BOOL)accessInstanceVariablesDirectly
返回NO,或最后也没又找到isKey
对应的成员变量时,系统还会给我们最后一次机会防止程序崩溃:就是实现- (id)valueForUndefinedKey:(NSString *)key
方法,该方法return nil;
时,会防止程序崩溃。
3.3 通过setValue:@"value" ForKey:@"key"
设置对象某一属性时:
- 首先,调用setter方法。对于一个属性,对应有2个setter方法,并且它们的优先级依次为:
setKey
>setIsKey
。
// main.m
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person setValue:@"young" forKey:@"name"];
}
return 0;
}
@end
// Person.m
#import "Person.h"
@implementation Person
- (void)setName:(NSString *)name {
// 运行结果:KVC-Test[2315:444214] setter name - young
NSLog(@"setter name - %@", name);
}
- (void)setIsName:(NSString *)name {
// 注释setName方法后,运行结果:KVC-Test[2315:444214] setter isName - young
NSLog(@"setter isName - %@", name);
}
@end
- 如果这个类没有实现setter方法,则系统就会调用类方法:
+ (BOOL)accessInstanceVariablesDirectly
默认返回YES; 如果该类方法返回值为NO,便如方法名所言,不能访问实例的所有成员变量。并且报错:setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
- 当然,如果
+ (BOOL)accessInstanceVariablesDirectly``+ (BOOL)accessInstanceVariablesDirectly
类方法返回NO时,系统还给我们最后一次防止报错的机会:实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key
方法。
#import "Person.h"
@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
// 运行结果:KVC-Test[2391:471126] Person类中没有name属性
NSLog(@"Person类中没有%@属性", key);
}
@end
3.4 将nil值通过KVC设置对象某一属性时,该属性不能为基本数据类型,否则报错:
如果实现了- (void)setNilValueForKey:(NSString *)key
方法,便可防止此异常的发生。
4.手撸KVC
// NSObject+MyKVX.h
#import
@interface NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key;
- (void)my_setValue:(id)value forKey:(NSString *)key;
@end
// NSObject+MyKVX.m
#import "NSObject+MyKVC.h"
#import
@implementation NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key {
// key值需要合法
if (key == nil || key.length == 0) {
NSLog(@"key is nil!!!");
return nil;
}
// step 1: 调用getter方法: getKey、key、getIsKey
NSString *getKey = [NSString stringWithFormat:@"get%@", key.capitalizedString];
// 调用getKey方法
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}
// 调用key方法
if ([self respondsToSelector:NSSelectorFromString(key)]) {
return [self performSelector:NSSelectorFromString(key)];
}
NSString *getIsKey = [NSString stringWithFormat:@"getIs%@", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(getIsKey)]) {
return [self performSelector:NSSelectorFromString(getIsKey)];
}
// step 2: 没有getter方法,调用countOfKey和objectInKeyAtIndex:(NSInteger)index方法
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@", key.capitalizedString];
// 注意⚠️:objectInKeyAtIndex:(NSInteger)index有参数,方法名要包含冒号
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:", key.capitalizedString];
NSUInteger keyCount = 0;
if ([self respondsToSelector:NSSelectorFromString(countOfKey)] &&
[self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
keyCount = (NSUInteger)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *keyValues = [NSMutableArray array];
for (int i = 0; i < keyCount; i++) {
[keyValues addObject:[self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(i)]];
}
return keyValues;
}
// step 3: 没有实现countOfKey和objectInKeyAtIndex方法
// 并且 accessInstanceVariablesDirectly类方法返回NO
if (![self.class accessInstanceVariablesDirectly]) { // 抛出异常
NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不让我访问成员变量,我能怎么办!" userInfo:nil];
@throw exception;
}
// 获取类中的所有成员变量
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷贝一个类的成员变量列表到堆区域
// step 4: 根据成员变量的优先级将获取成员变量的值
Ivar _keyIvar = nil;
Ivar _isKeyIvar = nil;
Ivar keyIvar = nil;
Ivar isKeyIvar = nil;
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivars[i];
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
_keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
_isKeyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
isKeyIvar = ivar;
}
}
id obj = nil;
if (_keyIvar) {
obj = object_getIvar(self, _keyIvar);
free(ivars);
return obj;
} else if (_isKeyIvar) {
obj = object_getIvar(self, _isKeyIvar);
free(ivars);
return obj;
} else if (keyIvar) {
obj = object_getIvar(self, keyIvar);
free(ivars);
return obj;
} else if (isKeyIvar) {
obj = object_getIvar(self, isKeyIvar);
free(ivars);
return obj;
}
// step 5: 调用valueForUndefinedKey方法
free(ivars);
return [self valueForUndefinedKey:key];
}
- (void)my_setValue:(id)value forKey:(NSString *)key {
// key值需要合法
if (key == nil || key.length == 0) {
NSLog(@"key is nil!!!");
return;
}
// 第一步:调用setter方法:setKey、setIsKey; 注意⚠️:setter方法有参数,所以方法名后有冒号
// capitalizedString方法:字符串首字母大写
NSString *setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
// 调用setKey方法
if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
[self performSelector:NSSelectorFromString(setKey) withObject:value];
return;
}
// 调用setIsKey
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
[self performSelector:NSSelectorFromString(setIsKey) withObject:value];
}
// 第二步:没有setter方法 并且 accessInstanceVariablesDirectly类方法返回NO
if (![self.class accessInstanceVariablesDirectly]) { // 抛出异常
NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不让我访问成员变量,我能怎么办!" userInfo:nil];
@throw exception;
return;
}
// 获取类中的所有成员变量
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷贝一个类的成员变量列表到堆区域
// 第三步:根据成员变量的优先级将value赋值给成员变量
Ivar _keyIvar = nil;
Ivar _isKeyIvar = nil;
Ivar keyIvar = nil;
Ivar isKeyIvar = nil;
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivars[i];
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
_keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
_isKeyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
isKeyIvar = ivar;
}
}
if (_keyIvar) {
object_setIvar(self, _keyIvar, value);
free(ivars);
return;
} else if (_isKeyIvar) {
object_setIvar(self, _isKeyIvar, value);
free(ivars);
return;
} else if (keyIvar) {
object_setIvar(self, keyIvar, value);
free(ivars);
return;
} else if (isKeyIvar) {
object_setIvar(self, isKeyIvar, value);
free(ivars);
return;
}
// 异常处理方法
[self setValue:value forUndefinedKey:key];
free(ivars);
}
@end