OC-KVC
文章目录
- 定义
- 四个方法
- 用法
- key
- keyPath
- 设值流程
- 验证
- 处理不存在的key
- 处理nil值
- KVC处理数值和结构体类型
- NSNumber
- NSValue
- 简介
- 类型
- 使用
- KVC键值验证
- KVC处理集合
- 简单集合运算符
- 对象运算符
- KVC处理字典
- 两个方法
- KVC应用
定义
KVC键值编码是指iOS开发中允许作者通过key名直接访问对象的属性或者赋值,而不是明确调用存取方法。使用KVC键值编码可以在运行时动态的访问和修改对象的属性,而不是在编译时确定
注:在实现了访问器方法的类中使用点语法与KVC访问对象差别不大。但是在没有实现访问器方法的类中,适合使用KVC
KVC是基于NSObject的扩展来实现的,OC语言中有一个显示NSKeyValueCoding类别名,所以所有继承了NSObject的类型都可以使用KVC。除了一些没有继承NSObject的纯Swift类和结构体不支持KVC。
四个方法
- (void)setValue:(id)value forKeyPath:(NSString* )keyPath;
- (void)setValue:(id)value forKey:(NSString* )key;
- (id)valueForKeyPath:(NSString* )keyPath;
- (id)valueForKey:(NSString* )key;
- key和keyPath的区别
- key只接受当前类所具有的属性,不管是自己的还是从父类继承过来的
- keyPath:除了能接受当前类的属性,还可以接受当前类的属性的属性(接受关系链)
用法
key
Coffee* coffee = [[Coffee alloc] init];[coffee setValue:@"10000" forKey:@"price"];NSLog(@"%@", [coffee valueForKey:@"price"]);
输出结果如下:
keyPath
coffee.sonOfCoffee = [[SonOfCoffee alloc] init];[coffee setValue:@"white" forKeyPath:@"sonOfCoffee.color"];NSLog(@"%@", [coffee.sonOfCoffee valueForKey:@"color"]);
输出结果如下:
设值流程
- 按照setKey、_setKey的顺序查找方法,如果找到直接赋值
- 没有找到的话,则调用
+ (BOOL)accessInstanceVariablesDirectly {return YES;
/*默认返回YES表示如果没有找到Set<Key>这个存取方法的话,将按照_key、_iskey、key、iskey的顺序搜索成员,如果重写方法设置为NO,在没有找到Set<Key>后直接停止搜索了。
*/
}
- KVC机制会去寻找该类中以传入的"_该字符串"的成员变量,大部分时间即属性创建时自动生成的成员变量,无论该成员变量是在接口部分还是实现部分定义,无论使用的是那个访问控制符修饰,这条KVC底层都是对其进行赋值
- 如果仍然没有找到,系统会执行该对象的setValue:forUndefinedKey:方法,这个方法会引发一个异常,导致程序结束,valueForKey方法类似,但是获取的是getter方法的返回值,没有找到成员变量会执行valueForUndefinedKey:方法,同义也会导致·程序终止
验证
#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface AUser : NSObject {@packageNSString* name;NSString* _name;
}@endNS_ASSUME_NONNULL_END
// Created by xiaoli pop on 2025/9/10.
//#import "AUser.h"@implementation AUser {int age;
}@end
// Created by xiaoli pop on 2025/9/9.
//#import <Foundation/Foundation.h>
#import "AUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {AUser* aUser = [[AUser alloc] init];[aUser setValue:@"strName1" forKey:@"name"];NSLog(@"name = %@", aUser->name);NSLog(@"_name = %@", aUser->_name);[aUser setValue:[NSNumber numberWithInt:10] forKey:@"age"];NSLog(@"age = %@", [aUser valueForKey:@"age"]);}return 0;
}
我们运行发现结果如下:
我们解释一下原因:首先我们使用KVC给属性赋值,首先查找set方法,没有找到,便按照_name、name的属性去查找。先搜索到的是_name,所以给_name赋值,name未被赋值。所以name为空。对于age成员变量来说,尽管是在类的实现部分定义的,也被赋值了
处理不存在的key
我们在使用KVC时,如果该属性没有setter、getter方法,也不存在对应的成员变量时,程序会调用setValue:forUndefinedKey: 或者valueForUndefinedKey:方法。系统默认该方法的实现是引发一个异常导致程序终止,但是我们可以重写这个方法,达到我们想要的效果
#import "AUser.h"@implementation AUser {int age;
}-(void)setValue:(id)value forUndefinedKey:(NSString *)key {NSLog(@"重写了setValue: forundefinedKey:方法");
}@end
#import <Foundation/Foundation.h>
#import "AUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {AUser* aUser = [[AUser alloc] init];[aUser setValue:@"nnnn" forKey:@"123"];}return 0;
}
输出如下结果:
处理nil值
假如我们在一个类中定义一个int类型的属性时,如果给他赋值为nil的话,会引发异常,因为int类型是不支持接受nil值的,也就是说,当程序尝试给某个属性赋nil时,如果该属性不能接受nil值,程序就会自动执行该对象的setNilValueForKey:方法。我们同样可以重写方法来达到我们想要的效果。示例如下:
#import "AUser.h"@implementation AUser {int age;
}- (void)setNilValueForKey:(NSString *)key {if ([key isEqualToString:@"age"]) {age = 0;} else {[super setNilValueForKey:key];}
}
// Created by xiaoli pop on 2025/9/9.
//#import <Foundation/Foundation.h>
#import "AUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {AUser* aUser = [[AUser alloc] init];[aUser setValue:nil forKey:@"age"];NSLog(@"age = %@", [aUser valueForKey:@"age"]);}return 0;
}
输出结果如下:
KVC处理数值和结构体类型
valueForKey:方法总会返回一个id对象,如果原本的类型变量是值类型或者结构体类型,返回值会封装成NSNumber或者NSValue对象,这两个类会处理数字、布尔值和结构体。
在使用setValue:forKey:时,我们需要手动将值类型转换为NSNumber或者NSValue类型才能传过去。因为传入和传出都是id类型,所以需要自己保证类型正确,运行时如果错会抛出异常
NSNumber
可以使用NSNumber的数据类型:
+ (NSNumber *)numberWithChar:(char)value;
+ (NSNumber *)numberWithUnsignedChar:(unsigned char)value;
+ (NSNumber *)numberWithShort:(short)value;
+ (NSNumber *)numberWithUnsignedShort:(unsigned short)value;
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithUnsignedInt:(unsigned int)value;
+ (NSNumber *)numberWithLong:(long)value;
+ (NSNumber *)numberWithUnsignedLong:(unsigned long)value;
+ (NSNumber *)numberWithLongLong:(long long)value;
+ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithInteger:(NSInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
NSValue
简介
NSValue是Foundation框架的一部分,用于封装基本数据类型,如结构体和枚举,允许我们将基本数据类型存储在对象中。
类型
+ (NSValue*)valueWithCGPoint:(CGPoint)point;
+ (NSValue*)valueWithCGSize:(CGSize)size;
+ (NSValue*)valueWithCGRect:(CGRect)rect;
+ (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;
+ (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
+ (NSValue*)valueWithUIOffset:(UIOffset)insetsNS_AVAILABLE_IOS(5_0);
任何结构体都可以都可以转化为NSValue对象,包括自定义的结构体
使用
我们可以使用valueWithBytes:objCType:方法来创建一个NSValue对象,这个方法接受两个参数,一个是指向数据的指针,另一个是描述数据类型的字符串
// Created by xiaoli pop on 2025/9/9.
//#import <Foundation/Foundation.h>
#import "AUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {CGPoint point = CGPointMake(54, 45);NSValue* pointValue = [NSValue valueWithBytes:&point objCType:@encode(CGPoint)];/*@encode()返回一个OC类型的字符串编码,可以是基本数据类型,也可以是结构体类型。*/NSArray* array = [NSArray arrayWithObject:pointValue];NSLog(@"%@", array);CGRect rect = CGRectMake(10, 10, 10, 10);NSValue* pointRect = [NSValue valueWithBytes:&rect objCType:@encode(CGRect)];NSArray* array2 = [NSArray arrayWithObject:pointRect];NSLog(@"%@", array2);}return 0;
}
KVC键值验证
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError {if ([inKey isEqualToString:@"name"]) {NSString* name = (NSString* )*ioValue;if (name.length == 0) {if (outError) {*outError = [NSError errorWithDomain:@"MyDomain" code:1001 userInfo:@{NSLocalizedDescriptionKey:@"NAme cannot be empty."}];}return NO;}}return YES;
}
我们通过重写这个方法,就可以对属性值进行验证
#import <Foundation/Foundation.h>
#import "AUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {NSError* error = nil;NSString* name = @"";AUser* aUser = [[AUser alloc] init];if (![aUser validateValue:&name forKey:@"name" error:&error]) {NSLog(@"Validation failed: %@", error.localizedDescription);} else {[aUser setValue:name forKey:@"name"];}}return 0;
}
输出结果如下:
KVC处理集合
简单集合运算符
简单的集合运算符:
@avg:平均值
@count:总个数
@max: 最大值
@min:最小值
@sum:总量
#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {BUser* b1 = [BUser new];b1.name = @"小李";b1.age = 10;BUser* b2 = [BUser new];b2.name = @"小吴";b2.age = 20;BUser* b3 = [BUser new];b3.name = @"小杨";b3.age = 30;NSArray* array = [NSArray arrayWithObjects:b1, b2, b3, nil];NSNumber* sum = [array valueForKeyPath:@"@sum.age"];NSLog(@"sum = %@", sum);NSNumber* avg = [array valueForKeyPath:@"@avg.age"];NSLog(@"avg = %@", avg);NSNumber* count = [array valueForKeyPath:@"@count.age"];NSLog(@"count = %@", count);NSNumber* min = [array valueForKeyPath:@"@min.age"];NSLog(@"min = %@", min);NSNumber* max = [array valueForKeyPath:@"@max.age"];NSLog(@"max = %@", max);}return 0;
}
输出结果如下:
对象运算符
以数组的形似返回。有下面几种情况
- unionOfObjects: 不去重
- distinctUnionOfObjects: 去重
- unionOfArrays:不去重
- distinctUnionOfArrays:去重
后面两种是针对二维数组的
示例:
#import <Foundation/Foundation.h>
#import "AUser.h"
#import "BUser.h"
int main(int argc, const char * argv[]) {@autoreleasepool {BUser* b1 = [BUser new];b1.name = @"小李";b1.age = 10;BUser* b2 = [BUser new];b2.name = @"小吴";b2.age = 20;BUser* b3 = [BUser new];b3.name = @"小杨";b3.age = 30;NSArray* array = [NSArray arrayWithObjects:b1, b2, b3, nil];NSArray* nameArray = [array valueForKeyPath:@"@unionOfObjects.name"];for (NSString* nameStr in nameArray) {NSLog(@"%@",nameStr);}}return 0;
}
输出结果如下:
KVC处理字典
两个方法
- (NSDictionary<NSString *,id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys {//输入一组key,返回这组key对应的属性,再组成一个字典返回
}
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues {//用来修改Model中对应的属性
}
示例:
@property (nonatomic, copy)NSString* country;
@property (nonatomic, copy)NSString* province;
@property (nonatomic, copy)NSString* city;
@property (nonatomic, copy)NSString* district;
int main(int argc, const char * argv[]) {@autoreleasepool {BUser* bUser = [BUser new];bUser.country = @"China";bUser.province = @"ShanXi";bUser.city = @"Ankang";bUser.district = @"HanBin";NSArray* array = @[@"country", @"province", @"city", @"district"];NSDictionary* dict = [bUser dictionaryWithValuesForKeys:array];NSLog(@"%@", dict);NSDictionary* modifyDict = @{@"country" : @"USA", @"province" : @"califonia", @"city" : @"ooo", @"district" : @"ppp"};[bUser setValuesForKeysWithDictionary:modifyDict];NSLog(@"country = %@, province = %@ , city = %@", bUser.country, bUser.province, bUser.city);}return 0;
}
KVC应用
- 动态的取值设值
- 使用KVC访问和修改私有变量
- Model和字典之间的转化
- 修改一些控件的内部属性
- 操作集合