KVC可以访问私有属性,有两个方法- (void)setValue:(nullable id)value forKey:(NSString *)key
和- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath
,这肯定大家都知道。接下来我们自定义一个KVC,从而了解一下它的实现过程。本文旨在探索他的实现过程,所以只针对对象类型,未对非对象类型进行检验。
查找规则
首先会根据key查找相关方法,如果没找到会再找相关成员变量。
赋值:
相关方法:set
:, _set :, setIs :
相关成员变量:_
, _is , , is
取值:
相关方法:
:, _ :
相关成员变量:_
_is is
了解上边的规则后,我们来创建一个NSObject的分类,因为要用到runtime,所以得打开runtime开关,并导入投文件#import
,仿照系统KVC创建自己的方法,如下:
赋值
- (void)sj_setValue:(nullable id)value forKey:(NSString *)key {
// 判断是否合法,key不能为空,否则会报UndefineKey的错误
if (key == nil && key.length == 0) {
return;
}
// Key
NSString* Key = key.capitalizedString;
/// 先找相关方法
//set:, _set:, setIs:
NSString* setKey = [NSString stringWithFormat:@"set%@:", Key];
if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
[self performSelector:NSSelectorFromString(setKey) withObject:value];
return;
}
NSString* _setKey = [NSString stringWithFormat:@"_set%@:", Key];
if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
[self performSelector:NSSelectorFromString(_setKey) withObject:value];
return;
}
NSString* setIsKey = [NSString stringWithFormat:@"setIs%@:", Key];
if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
[self performSelector:NSSelectorFromString(setIsKey) withObject:value];
return;
}
// 如果accessInstanceVariablesDirectly返回NO的话,表示不允许查找成员变量,代码就不会往下走了,默认为YES
if (![self.class accessInstanceVariablesDirectly]) {
NSException* exception = [NSException exceptionWithName:@"NSUnkonwnKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
@throw exception;
}
/// 再找相关变量
/// 获取所以成员变量- 运用到runtime
unsigned int count = 0;
Ivar* ivars = class_copyIvarList([self class], &count);// 获取成员变量的列表和数量
NSMutableArray* arr = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
Ivar var = ivars[i];
const char* varName = ivar_getName(var);
NSString* name = [NSString stringWithUTF8String:varName];
[arr addObject:name];
}
// _ _is is
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
object_setIvar(self, ivars[i], value);// 给成员变量赋值
free(ivars);
return;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", Key]]) {
object_setIvar(self, ivars[i], value);
free(ivars);
return;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
object_setIvar(self, ivars[i], value);
free(ivars);
return;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", Key]]) {
object_setIvar(self, ivars[i], value);
free(ivars);
return;
}
}
// 如果都没找到,手动出发报错
[self setValue:value forUndefinedKey:key];
free(ivars);//这里要手动释放
}
可以在操作的类中实现下面这个方法,来控制是否可以查找成员变量
// 是否允许查找成员变量,默认为YES
+(BOOL)accessInstanceVariablesDirectly {
return NO;
}
取值
跟赋值一模一样,前提是按照前边的规则来取:
- (id)sj_valueForKey:(NSString *)key {
// 判断是否合法
if (key == nil && key.length == 0) {
return nil;
}
/// 先找相关方法
// :, _:
NSString* Key = [NSString stringWithFormat:@"get%@", key];
if ([self respondsToSelector:NSSelectorFromString(Key)]) {
id value = [self performSelector:NSSelectorFromString(Key)];
return value;
}
NSString* _Key = [NSString stringWithFormat:@"_%@", key];
if ([self respondsToSelector:NSSelectorFromString(_Key)]) {
id value = [self performSelector:NSSelectorFromString(_Key)];
return value;
}
if (![self.class accessInstanceVariablesDirectly]) {
NSException* exception = [NSException exceptionWithName:@"NSUnkonwnKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
@throw exception;
}
/// 再找相关变量
/// 获取所以成员变量
unsigned int count = 0;
Ivar* ivars = class_copyIvarList([self class], &count);
NSMutableArray* arr = [[NSMutableArray alloc] init];
for (int i = 0; i < count; i++) {
Ivar var = ivars[i];
const char* varName = ivar_getName(var);
NSString* name = [NSString stringWithUTF8String:varName];
[arr addObject:name];
}
// _ _is is
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
// object_setIvar(self, ivars[i], value);
id value = object_getIvar(self, ivars[i]);
free(ivars);
return value;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", Key]]) {
id value = object_getIvar(self, ivars[i]);
free(ivars);
return value;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
id value = object_getIvar(self, ivars[i]);
free(ivars);
return value;
}
}
for (int i = 0; i < count; i++) {
NSString* keyName = arr[i];
if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", Key]]) {
id value = object_getIvar(self, ivars[i]);
free(ivars);
return value;
}
}
[self valueForUndefinedKey:key];
free(ivars);
return nil;
}
容错
1.key不能为空,不管是设值还是取值。
2.对非对象类型,值不能为空。
所以我们在类中实现以下几个方法来防止崩溃:
// 对非对象类型,值不能为空
- (void) setNilValueForKey:(NSString *)key {
NSLog(@"%@ 的值不能为空", key);
}
// 赋值key值不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"key = %@不存在 ", key);
}
// 取值key值不存在
- (id) valueForUndefinedKey:(NSString *)key {
NSLog(@"key = %@ 的值不存在", key);
return nil;
}
校验
比如定义一个Persion类,加一个age的属性,可以在.m中验证age的值,这个在实际开发中应用会比较广,用于限制属性的合法性:
- (BOOL) validateAge:(inout id _Nullable __autoreleasing *)ioValue error:(out NSError * _Nullable __autoreleasing *)outError {
NSNumber* value = (NSNumber*)*ioValue;
NSLog(@"%@", value);
if (value <= 0 || value >= 200) {
return NO;
}
return YES;
}
方法名要对应属性名,比如属性为age,那么方法名就是validateAge:....
调用
- (void)viewDidLoad {
[super viewDidLoad];
Person* p = [Person new];
NSNumber* value = @200;
if ([p validateValue:&value forKey:@"age" error:NULL]) {
[p setValue:value forKey:@"age"];
}
}