KVC 相关类及方法 ? 1. 定义 : KVC(Key-value coding)键值编码 从定义上很明显了解到KVC是基于key - value 的一些操作 而最容易想到的是NSDictionary 操作了 NSDictionary *dic = [[NSDictionary alloc]init]; [dic setValue:@"23" forKey:@"age"]; [dic objectForKey:@"age"]; 2. 接着, 我们进入 NSDictionary.h 系统文件 发现NSDictionary实例中并没有实现setValue:forKey: 和 objectForKey: 方法 (也不在NSObject中) 接着我们之间点击上面[dic setValue:@"23" forKey:@"age"]; 跳转进入系统方法 : 我们进入到了系统类NSKeyValueCoding.h 3. NSKeyValueCoding.h 文件的声明 : @interface NSObject(NSKeyValueCoding) 发现有熟悉的一些方法 : - (nullable id)valueForKey:(NSString *)key; - (void)setValue:(nullable id)value forKey:(NSString *)key; - (nullable id)valueForKeyPath:(NSString *)keyPath; - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; 等等还有其他方法,下面再解释 发现KVC 是基于NSObject 扩展功能 KVO是基于KVC基础上 同样也基于其他类扩展一些方法, 比如 : - (NSMutableArray *)mutableArrayValueForKey:(NSString *)key; 因此, 任何NSObject (或子类)对象都支持KVC方法 那么问题来了, 对象forKey对应属性可能不存在 ? 这就涉及到runtime了 KVC 动态性 说明 : KVC(Key-value coding)键值编码,就是指iOS的开发中,可以允许开发者通 过Key名(字符串)直接访问对象的属性,或者给对象的属性赋值。而不需要调用明 确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编 译时确定 因此方法 [dic objectForKey:@"age"]; 编译时不管dic 是否存在属性age, 而是到 运行的时候才会正常,或者抛出错误 既然是在运行时动态地访问和修改, 那么必定涉及消息转发和异常处理了 (runtime) 底层如何实现? 如何消息转发 ? 注意 : KVC 可以显式或者隐式获取或者修改一些属性值 , 包括 自定义和系统私有属性 但是前提条件是字符串key对应的属性一定要存在, 编译不会对字符串key进行校 验,运行时失败会导致异常 KVC 底层 (1 设置值). 当调用[user setValue:@“abc” forKey:@”name“]; 的代码时, 下面看 看怎么执行的 : 设值步骤 : 1.1. 首先调用 setName: 方法 , 如果找不到就执行下一步 说明, 声明一个属性就自动生成set和get方法 : @property(nonatomic,strong)NSString *name; 没有声明上面属性就没有setName方法 1.2. 检查+ (BOOL)accessInstanceVariablesDirectly 方法返回值 NO : 1.3. 执行setValue:forUndefinedKey:方法 A. 可以重写这个方法处理异常 B. 没有处理时,直接抛异常,返回null YES : 1.4. 按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作 1.5. 如果还是没有, 再执行setValue:forUndefinedKey:方法 补充变量的几种声明方式 : @interface Test: NSObject { NSString *_name; //方式1 NSString *_isName; //方式2 NSString *name; //方式3 NSString *isName; //方式4 } ... @property(nonatomic,copy)NSString *name; //方式0 (2 取值) 当调用valueForKey:@”name“的代码时 取值步骤 : 当调用valueForKey:取值时: 1. 首先按照 getName , name , isName 顺序方法查找getter方法 如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象 -(NSString *)getName { ... } -(NSString *)name { ... } -(NSString *)isName { ... } 2. 如果上面的getter没有找到,KVC则会查找NSArray的方法 : 先是 : countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方 法 , get<Key>:range: 没有再找 : countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的 方法 3. 如果NSArray找不到, 就响应NSSet方法 countOf<Key>,enumeratorOf<Key>,memberOf<Key> 4. 如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly 如果返回YES(默认行为): 会按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量名 返回NO的话 直接调用valueForUndefinedKey:方法,默认是抛出异常 (3) KVC使用keyPath 需求 : 当目标是对象的属性的属性 时(深层关系) 时, 该如何处理 ? KVC提供了一个解决方案,那就是键路径keyPath - (nullable id)valueForKeyPath:(NSString *)keyPath; //通过 KeyPath来取值 - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过 KeyPath来设值 使用 : 比如设置: user.type.name [user setValue:@“student” forKeyPath:@"type.name"]; (4) KVC 异常处理 : 编译无法对字符串进行语法校验, 这也是导致KVC使用容易出现异常的根本原因 : 1. KVC处理nil异常 设置空值 - (void)setNilValueForKey:(NSString *)key { NSLog(@"不能将%@设成nil", key); } 2. KVC处理UndefinedKey异常 属性或者方法不存在 - (id)valueForUndefinedKey:(NSString *)key { NSLog(@"出现异常,该key不存在%@",key); return nil; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"出现异常,该key不存在%@", key); } (5) KVC键值验证 既然编译无法检验语法, 那么就需要 提前手动验证是否能成功 KVC提供了验证Key对应的Value是否可用的方法: 使用test例子: @autoreleasepool { //Test生成对象 Test *test = [[Test alloc] init]; //通过KVC设值test的age NSNumber *age = @10; NSError* error; NSString *key = @"age"; BOOL isValid = [test validateValue:&age forKey:key error:&error]; if (isValid) { NSLog(@"可以匹配再进行设置值 :”); [test setValue:age forKey:key]; } else { NSLog(@"键值不匹配"); } //通过KVC取值age打印 NSLog(@"test的年龄是%@", [test valueForKey:@"age"]); } (6) KVC属性类型要求 : 重要 : KVC 处理的值必须是NSObject 对象或其子类 NSString NSValue (注意) NSNumber (注意) 其它常见等等 1. KVC 总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值 会封装成NSNumber或者NSValue对象 需要转 NSNumber : BOOL NSInteger int , unsignedint long , unsignedlong , longlong , unsignedlonglong float , double ... char, unsignedchar short, unsignedshort 需要转 NSValue : CGPoint , CGSize , CGRect , CGAffineTransform UIEdgeInsets, UIOffset 实例 : 1. 比如age是NSInteger对象(转NSNumber): //设置值 [obj setValue:@(6) forKey:@"age"]; //错误使用返回 NSInteger age = [obj valueForKey:@"age"]; //正确使用返回 NSNumber *age = [obj valueForKey:@"age"]; 2. BOOL值(转NSNumber) //设置 [obj setValue:@(YES) forKey:@"girl"]; //正确使用, 转换bool值 BOOL age = [[obj valueForKey:@"girl"] boolValue]; 3. CGSize转NSValue + (NSValue*)valueWithCGSize:(CGSize)size; (7) KVC处理集合 : KVC除了能获取单个对象属性, 还能对列表所有对象的属性进行简单或复杂运算 1. 简单集合运算符共有@avg, @count , @max , @min ,@sum5种 2. 对象运算符@distinctUnionOfObjects , @unionOfObjects 实例 : 简单运算 //一个班级学生 NSArray* arrPerson = @[person1,person2,person3,person4]; //平均年龄 NSNumber* avg = [arrPerson valueForKeyPath:@"@avg.age"]; //学生数 NSNumber* count = [arrPerson valueForKeyPath:@"@count"]; //最小年龄 NSNumber* min = [arrPerson valueForKeyPath:@"@min.age"]; //最大年龄 NSNumber* max = [arrPerson valueForKeyPath:@"@max.age"]; //年龄总值 NSNumber* sum = [arrPerson valueForKeyPath:@"@sum.age"]; 对象运算符 //不同年龄集合(去重) NSArray* arrAges = [arrPerson valueForKeyPath:@"@distinctUnionOfObjects.age"]; //不同年龄集合(不去重) NSArray* arrUnionAges = [arrPerson valueForKeyPath:@"@unionOfObjects.age"]; 高阶消息传递 : NSArray* arrStr = @[@"abc",@"223",@"hjj"]; //返回所有字符串新数组 NSArray* arrCapStr = [arrStr valueForKey:@"capitalizedString"]; //返回字符串长度number数组 NSArray* arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"]; (8) KVC 处理字典 : 常见的就是字典解析了 1. 模型转字典 //把对应key所有的属性全部取出来 NSDictionary* dict = [model dictionaryWithValuesForKeys:arr]; 2. 字典转模型 //用key Value来修改Model的属性 NSDictionary* dict = @{@"name":@"aa" ... } [model setValuesForKeysWithDictionary: dict]; (9) 访问和修改私有变量 : 在xcode11之前 我么经常会使用KVC 访问和修改私有变量 比如导航,tabbar修改,背景图等等 比如UITextField中的placeHolderText 但是 : 在Xcode 11 之后开始限制了,部分控件 访问和修改私有变量 会直接崩溃.