「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。
三、KVC 使用场景
估计都会使用就不举例了
-
利用KVC动态的取值和设值
常用的可以通过setValue:forKey: 和 valueForKey:
也可以通过路由的方式setValue:forKeyPath: 和 valueForKeyPath:
-
Model和字典转换
-
用KVC来访问和修改私有变量
根据上面的实现原理我们知道,
KVC本质上是操作方法列表以及在内存中查找实例变量。我们可以利用这个特性访问类的私有变量,例如下面在.m中定义的私有成员变量和属性,都可以通过KVC的方式访问。【对于KVC而言,一个对象没有自己的隐私,所以可以通过KVC修改和访问任何私有属性】这个操作对readonly的属性,@protected的成员变量,都可以正常访问。如果不想让外界访问类的成员变量,则可以将
accessInstanceVariablesDirectly属性赋值为NO。 -
修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些控件的API(常用的就是自定义
tabbar、个性化UITextField中的placeHolderText),这样我们就无法正常地访问和修改这些控件的样式。而KVC在大多数情况可下可以解决这个问题。 -
用KVC实现高阶消息传递
在对容器类使用KVC时,valueForKey:将会被传递给容器中的每一个对象,而不是对容器本身进行操作,结果会被添加到返回的容器中,这样,可以很方便的操作集合 来返回 另一个集合
//KVC实现高阶消息传递
- (void)transmitMsg{
NSArray *arrStr = @[@"english", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr) {
NSLog(@"%@", str);
}
NSArray *arrCapStrLength = [arrCapStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in arrCapStrLength) {
NSLog(@"%ld", (long)length.integerValue);
}
}
//********打印结果********
11:33:43.377672+0800 CJLCustom[60035:6380757] English
11:33:43.377773+0800 CJLCustom[60035:6380757] Franch
11:33:43.377860+0800 CJLCustom[60035:6380757] Chinese
11:33:43.378233+0800 CJLCustom[60035:6380757] 7
11:33:43.378327+0800 CJLCustom[60035:6380757] 6
11:33:43.378417+0800 CJLCustom[60035:6380757] 7
四、拓展
KVC属性验证
KVC提供了属性值,用来验证key对应的Value是否可用的方法
- 在调用KVC时可以先进行验证,验证通过下面两个方法进行,支持
key和keyPath两种方式。验证方法默认实现返回YES,可以通过重写对应的方法修改验证逻辑。
验证方法需要我们手动调用,并不会在进行KVC的过程中自动调用。
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
这个方法的默认实现是去探索类里面是否有一个这样的方法:
-(BOOL)validate<Key>:error:如果有这个方法,就调用这个方法来返回,没有的话就直接返回YES
@implementation Address
-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{ //在implementation里面加这个方法,它会验证是否设了非法的value
NSString* country = *value;
country = country.capitalizedString;
if ([country isEqualToString:@"Japan"]) {
return NO; //如果国家是日本,就返回NO,这里省略了错误提示,
}
return YES;
}
@end
NSError* error;
id value = @"japan";
NSString* key = @"country";
BOOL result = [add validateValue:&value forKey:key error:&error]; //如果没有重写-(BOOL)-validate<Key>:error:,默认返回Yes
if (result) {
NSLog(@"键值匹配");
[add setValue:value forKey:key];
}
else{
NSLog(@"键值不匹配"); //不能设为日本,其他国家都行
}
NSString* country = [add valueForKey:@"country"];
NSLog(@"country:%@",country);
//打印结果
KVCDemo[867:58871] 键值不匹配
KVCDemo[867:58871] country:China
KVC处理非对象
KVC是支持基础数据类型和结构体的,可以在setter和getter的时候,通过NSValue和NSNumber来转换为OC对象。该方法valueForKey:总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。这两个类会处理从数字,布尔值到指针和结构体任何类型。然后开发者需要手动转换成原来的类型。尽管valueForKey:会自动将值类型封装成对象,但是setValue:forKey:却不行。你必须手动将值类型转换成NSNumber或者NSValue类型,才能传递过去。
- 可以调用initWithBool:方法对基础数据类型进行包装
@property (nonatomic, assign, readonly) BOOL boolValue;
- (NSNumber *)initWithBool:(BOOL)value
KVC异常处理
-
- key或者keyPath发生错误
当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash
我们可以重写下面两个方法:
//获取了不存在的key 只需要实现如下方法
- (nullable id)valueForUndefinedKey:(NSString *)key;
//设置了不存在的key 造成崩溃 只需要实现如下方法
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
-
- 传参为nil
通常情况下,KVC不允许你要在调用setValue:属性值 forKey:(或者keyPath)时对非对象传递一个nil的值。因为值类型是不能为nil的。如果你不小心传了,KVC会调用setNilValueForKey:方法。这个方法默认是抛出异常,所以一般而言最好还是重写这个方法。
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"-----设置了nil");
}