目录
KVC 简介
-
相关文档
-
KVC 的概念
KVC(Key-Value Coding)翻译成中文叫:键值编码,是由
NSObject的非正式协议(即NSObject的分类)NSKeyValueCoding启用的一种机制,用于间接地访问对象的属性与成员变量(即,通过字符串来访问对象的属性与成员变量)。遵守了NSKeyValueCoding非正式协议的对象会提供对其属性与成员变量的间接访问(即,继承自NSObject的对象都拥有 KVC 机制,都能调用 KVC 的相关方法)。KVC 的这种间接访问机制,补充了对象的属性与成员变量所提供的直接访问机制KVC 是 iOS 开发中的黑魔法之一,通过 KVC 可以在程序运行时动态地获取和设置对象的属性与成员变量,很多高级的 iOS 开发技巧都是基于 KVC 实现的。同时,KVC 也是许多其他 Cocoa 技术的基础,比如 KVO、Cocoa bindings、Core Data、AppleScript-ability 等等
-
KVC 的相关方法
KVC 所有方法的默认实现都在
NSObject的分类NSKeyValueCoding中,子类可以重写相关方法,提供自定义的实现// KVC 的相关方法都定义在该头文件下 #import <Foundation/NSKeyValueCoding.h> #pragma mark - 获取属性或者成员变量的值 // 获取方法调用者中给定 key 所标识的属性或者成员变量的值 -(nullable id)valueForKey:(NSString *)key; // 获取方法调用者中给定 keyPath 所标识的属性或者成员变量的值 -(nullable id)valueForKeyPath:(NSString *)keyPath; // 在通过 KVC 取值时,如果没有搜索到任何跟 key 或者 keyPath 有关的属性与成员变量,则会调用该方法。该方法默认会抛出 NSUnknownKeyException 异常 // 开发者可以通过重写该方法,以更优雅的方式处理 KVC 在取值时 key 或者 keyPath 未搜索到的情况 -(nullable id)valueForUndefinedKey:(NSString *)key; #pragma mark - 设置属性或者成员变量的值 // 将方法调用者中给定 key 所标识的属性或者成员变量的值设置为给定的 value -(void)setValue:(nullable id)value forKey:(NSString *)key; // 将方法调用者中给定 keyPath 所标识的属性或者成员变量的值设置为给定的 value -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 在通过 KVC 赋值时,如果没有搜索到任何跟 key 或者 keyPath 有关的属性与成员变量,则会调用该方法。该方法默认会抛出 NSUnknownKeyException 异常 // 开发者可以通过重写该方法,以更优雅的方式处理 KVC 在赋值时 key 或者 keyPath 未搜索到的情况 -(void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // 在通过 KVC 赋值时,如果向非对象指针类型的属性或者成员变量传 nil,则会调用该方法。该方法默认会抛出 NSInvalidArgumentException 异常 // 开发者可以通过重写该方法,以更优雅的方式处理 KVC 在赋值时向非对象指针类型的属性或者成员变量传 nil 的情况 -(void)setNilValueForKey:(NSString *)key; #pragma mark - KVC 访问权限控制 // 用于标识:在通过 KVC 取值或者赋值时,如果没有搜索到相应的 getter 或者 setter,是否可以直接访问对象的成员变量。默认返回 YES +(BOOL)accessInstanceVariablesDirectly; #pragma mark - 进行字典与模型的相互转换 // 用于字典转模型:输入一个字典,获取字典中的 key-value,并设置模型中该 key 对应的 value // 如果字典中 key 对应的 value 为 NSNull 对象,则会先将获取到的 NSNull 对象拆箱成 nil,然后再赋值给对应的 value -(void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues; // 用于模型转字典:输入一组 key,获取模型中该组 key 对应的 value,并将获取到的 key-value 封装成字典返回 // 如果获取到的 value 是 nil,则会先将获取到的 nil 值装箱成 NSNull 对象,然后再添加到要返回的字典中 -(NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys; #pragma mark - 获取集合类型的属性或者成员变量 // 获取方法调用者中给定 key 所标识的 NSMutableArray 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableArray 对象) -(NSMutableArray *)mutableArrayValueForKey:(NSString *)key; // 获取方法调用者中给定 keyPath 所标识的 NSMutableArray 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableArray 对象) -(NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath; // 获取方法调用者中给定 key 所标识的 NSMutableOrderedSet 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableOrderedSet 对象) -(NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key; // 获取方法调用者中给定 keyPath 所标识的 NSMutableOrderedSet 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableOrderedSet 对象) -(NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath; // 获取方法调用者中给定 key 所标识的 NSMutableSet 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableSet 对象) -(NSMutableSet *)mutableSetValueForKey:(NSString *)key; // 获取方法调用者中给定 keyPath 所标识的 NSMutableSet 类型的属性或者成员变量(返回的集合代理对象表现为一个 NSMutableSet 对象) -(NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath; #pragma mark - 验证属性或者成员变量的值的合法性 // 验证要设置给属性或者成员变的值的合法性 -(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;
KVC 的基本使用
有 Dog 类和 Person 类如下所示:


-
通过 key 获取和设置实例对象的属性或者成员变量
-(void)kvcDemo { Person* aPerson = [[Person alloc] init]; // 赋值 [aPerson setValue:@"hcg" forKey:@"name"]; // 取值 NSString* aName = [aPerson valueForKey:@"name"]; // 输出结果 NSLog(@"aName = %@", aName); // aName = hcg } -
通过 keyPath 获取和设置实例对象的属性或者成员变量
keyPath(键路径 or 路由 )用于支持多级访问,其用法跟点语法相同
-(void)kvcDemo { Person* aPerson = [[Person alloc] init]; Dog* aDog = [[Dog alloc] init]; aPerson.petAnimal = aDog; // 赋值 [aPerson setValue:@"tom" forKeyPath:@"petAnimal.nickname"]; // 取值 NSString* aNickname = [aPerson valueForKeyPath:@"petAnimal.nickname"]; // 输出结果 NSLog(@"petAnimal.nickname = %@", aNickname); // petAnimal.nickname = tom }
KVC 对(非对象指针类型的值)的处理
仔细观察 KVC 取值和赋值的接口方法,我们会发现值 value 都被定义为对象类型 id。那么如何通过 KVC 获取和设置 基本数据类型或者结构体类型 的属性与成员变量呢?答案是使用拆箱操作和装箱操作:
- 在使用 KVC 进行取值时,如果获取的是非对象类型的值,则 KVC 会使用该值初始化一个
NSNumber对象(用于基本数据类型)或者NSValue对象(用于结构体类型),然后返回该对象。调用者需要调用拆箱操作,以提取对象里面存储的真实数值 - 在使用 KVC 进行赋值时,如果设置的是非对象类型的值,则调用者需要使用该值初始化一个
NSNumber对象(用于基本数据类型)或者NSValue对象(用于结构体类型),然后传递该对象。KVC 内部会调用拆箱操作,以提取对象里面存储的真实数值
-
KVC 对(基本数据类型的值)的处理
下表是 KVC 对于基本数据类型和
NSNumber对象之间的转换:基本数据类型 装箱操作 拆箱操作 BOOLnumberWithBool:boolValue(in iOS) /charValue(in macOS)charnumberWithChar:charValuedoublenumberWithDouble:doubleValuefloatnumberWithFloat:floatValueintnumberWithInt:intValuelongnumberWithLong:longValuelong longnumberWithLongLong:longLongValueshortnumberWithShort:shortValueunsigned charnumberWithUnsignedChar:unsignedCharunsigned intnumberWithUnsignedInt:unsignedIntunsigned longnumberWithUnsignedLong:unsignedLongunsigned long longnumberWithUnsignedLongLong:unsignedLongLongunsigned shortnumberWithUnsignedShort:unsignedShort代码示例:

-(void)kvcDemo { Person* aPerson = [[Person alloc] init]; // 赋值 NSNumber* num0 = [NSNumber numberWithInt:20]; [aPerson setValue:num0 forKey:@"age"]; // 取值 NSNumber* num1 = [aPerson valueForKey:@"age"]; int anAge = [num1 intValue]; // 输出结果 NSLog(@"anAge = %d", anAge); // anAge = 20 } -
KVC 对(结构体类型的值)的处理
下表是 KVC 对于结构体类型和
NSValue对象之间的转换:基本数据类型 装箱操作 拆箱操作 CGPointvalueWithCGPoint:CGPointValueCGRectvalueWithCGRect:CGRectValueCGSizevalueWithCGSize:CGSizeValueNSRangevalueWithRange:rangeValue除了以上
CGPoint、CGRect、CGSize、NSRange类型的结构体可以和NSValue对象之间进行相互转换,开发者自定义的结构体也可以装箱成NSValue对象,示例如下:
-(void)kvcDemo { Person* aPerson = [[Person alloc] init]; // 赋值 ThreeFloats threeDimensional0 = { 100.0f, 100.0f, 100.0f}; NSValue* value0 = [NSValue valueWithBytes:&threeDimensional0 objCType:@encode(ThreeFloats)]; [aPerson setValue:value0 forKey:@"threeDimensional"]; // 取值 NSValue* value1 = [aPerson valueForKey:@"threeDimensional"]; ThreeFloats threeDimensional1; [value1 getValue:&threeDimensional1]; // 输出结果 NSLog(@"threeDimensional1 = {%f, %f, %f}", threeDimensional1.x, threeDimensional1.y, threeDimensional1.z); // threeDimensional1 = {100.000000, 100.000000, 100.000000} } -
注意
① 因为 Swift 中的所有属性都是对象,所以这里的拆箱操作和装箱操作仅适用于 Objective-C 属性
② 当使用 KVC 进行赋值时(
setValue:forKey:、setValue:forKeyPath:),如果key对应的属性或者成员变量的数据类型不是对象指针类型,则value就禁止传nil。否则会调用异常处理方法setNilValueForKey:,该方法的默认实现为抛出异常NSInvalidArgumentException,并导致程序 Crash
KVC 的搜索模式
-
基本的 Getter 搜索模式
valueForKey:用于获取方法调用者中给定 key 所标识的属性或者成员变量的值,其默认实现会在方法调用者所属的类中执行以下操作:-
按照
get<Key>、<key>、is<Key>、(_get<Key>、_<key>)的顺序在方法调用者所属的类中查找 getter 方法
如果找到相应的 getter 方法,则调用之
如果 getter 方法的返回值类型是对象指针类型,则直接返回结果
如果 getter 方法的返回值类型是NSNumber支持的标量类型之一,则将返回值装箱成NSNumber类型的对象并返回
如果 getter 方法的返回值类型是NSValue支持的结构体类型之一,则将返回值装箱成NSValue类型的对象并返回(在 MacOS 10.5 中:任意类型的结构体都将转换为NSValues,而不仅仅是NSPoint、NRange、NSRect、NSSize) -
(在 MacOS 10.7 中引入)(没有找到简单的访问器方法)
在方法调用者所属的类中查找
-countOf<Key>
-indexIn<Key>OfObject:(对应于-[NSOrderedSet indexOfObject:])
-objectIn<Key>AtIndex:(对应于-[NSOrderedSet objectAtIndex:])
-<key>AtIndexes:(对应于-[NSOrderedSet objectsAtIndexes:])
如果找到一个 count 方法和一个 indexOf 方法,以及另外两个可能的方法中的至少一个,则返回响应所有NSOrderedSet方法的集合代理对象(集合代理对象NSKeyValueOrderedSet为NSOrderedSet的子类)
发送到集合代理对象的每个NSOrdereredSet消息将会被转换成方法调用者所属的类中以下方法的某些组合
-countOf<Key>、-indexIn<Key>OfObject:、-objectIn<Key>AtIndex:、-<key>AtIndexes:
如果在方法调用者所属的类中还实现了一个名称为-get<Key>:range:的可选方法,则该可选方法将在适当的时候被调用以获得最佳性能 -
(如果没有找到简单的访问器方法,没有找到
NSOrderedSet的相关访问方法)
在方法调用者所属的类中查找
-countOf<Key>
-objectIn<Key>AtIndex:(对应于-[NSArray objectAtIndex:])
-<key>AtIndexes:(对应于-[NSArray objectsAtIndexes:])(在 MacOS 10.4 中引入)
如果找到一个 count 方法和另外两个可能方法中的一个,则返回响应所有NSArray方法的集合代理对象(集合代理对象NSKeyValueArray为NSArray的子类)
发送到集合代理对象的每个NSArray消息将会被转换成方法调用者所属的类中以下方法的某些组合:
-countOf<Key>、-objectIn<Key>AtIndex:、-<key>AtIndexes:
如果在方法调用者所属的类中还实现了一个名称为-get<Key>:range:的可选方法,则该可选方法将在适当的时候被调用以获得最佳性能 -
(在 MacOS 10.4 中引入)(没有找到简单的访问器方法,没有找到
NSOrderedSet的相关访问方法,没有找到NSArray的相关访问方法)
在方法调用者所属的类中查找
-countOf<Key>
-enumeratorOf<Key>
-memberOf<Key>:(对应于-[NSSet member])
如果找到所有这三个方法,则将返回响应所有NSSet方法的集合代理对象(集合代理对象NSKeyValueSet为NSSet的子类)
发送到集合代理对象的每个NSSet消息将会被转换成方法调用者所属的类中以下方法的某些组合:
-countOf<Key>、-enumeratorOf<Key>、-memberOf<Key>: -
(没有找到简单的访问器方法,没有找到
NSOrderedSet的相关访问方法,没有找到NSArray的相关访问方法,没有找到NSSet的相关访问方法)
如果方法调用者的类属性+accessInstanceVariablesDirectly返回YES
则按照_<key>、_is<Key>、<key>、is<Key>的顺序在方法调用者中查找成员变量
如果找到这样的成员变量,则返回方法调用者中该成员变量的值,并且与步骤 1 一样,查看是否需要转换成NSNumber或NSValue类型的对象 -
(没有找到简单的访问器方法,没有找到
NSOrderedSet的相关访问方法,没有找到NSArray的相关访问方法,没有找到NSSet的相关访问方法,没有找到相关的成员变量)
调用-valueForUndefinedKey:并返回调用结果。-valueForUndefinedKey:的默认实现是抛出异常NSUnknownKeyException,并导致程序 Crash。开发者可以重写该方法根据特定的 key 做一些特殊处理
代码举例如下:
// Person.h #import <Foundation/Foundation.h> @interface Person : NSObject @end// Person.m #import "Person.h" @interface Person () { @private NSString* _name; @private NSString* _isName; @private NSString* name; @private NSString* isName; } @end @implementation Person #pragma mark - ① 查找 getter 方法 -(NSString *)getName { return @"getter method: getName"; } -(NSString *)name { return @"getter method: name"; } -(NSString *)isName { return @"getter method: isName"; } -(NSString *)_getName { return @"getter method: _getName"; } -(NSString *)_name { return @"getter method: _name"; } #pragma mark - ② 查找 NSOrderedSet 的相关方法 -(NSInteger)countOfName { return 5; } -(NSUInteger)indexInNameOfObject:(id)object { return 3; } -(id)objectInNameAtIndex:(NSUInteger)idx { return @"tom"; } -(NSArray<id> *)nameAtIndexes:(NSIndexSet *)indexes { NSMutableArray* mArr = [NSMutableArray array]; for (int i = 0; i < indexes.count; i++) { [mArr addObject:@"jack"]; } return mArr; } #pragma mark - ③ 查找 NSArray 的相关方法 -(NSInteger)countOfName { return 4; } -(id)objectInNameAtIndex:(NSUInteger)idx { return @"kang"; } -(NSArray<id> *)nameAtIndexes:(NSIndexSet *)indexes { NSMutableArray* mArr = [NSMutableArray array]; for (int i = 0; i < indexes.count; i++) { [mArr addObject:@"jack"]; } return mArr; } #pragma mark - ④ 查找 NSSet 的相关方法 -(NSInteger)countOfName { return 3; } -(NSEnumerator *)enumeratorOfName { NSSet* set = [NSSet setWithObjects:@"1", @"2", @"3", nil]; NSEnumerator* enumerator = [set objectEnumerator]; return enumerator; } -(nullable id)memberOfName:(id)object { return @"michael"; } #pragma mark - ⑤ 查找成员变量 +(BOOL)accessInstanceVariablesDirectly { return YES; } -(instancetype)init { if (self = [super init]) { _name = @"variable: _name"; _isName = @"variable: _isName"; name = @"variable: name"; isName = @"variable: isName"; } return self; } #pragma mark - ⑥ 处理 key 对应的属性或者成员变量查找不到的情况 -(id)valueForUndefinedKey:(NSString *)key { NSLog(@"method name = %s, key = %@", __func__, key); return @"hcg"; } @end// 调用示例 -(void)kvcDemo { Person* aPerson = [[Person alloc] init]; id value = [aPerson valueForKey:@"name"]; Class valueCls = [value class]; Class valueSuperCls = [value superclass]; NSLog(@"value.class = %@", valueCls); NSLog(@"value.superClass = %@", valueSuperCls); NSLog(@"value = %@", value); // 如果通过 KVC 获取到的是 NSOrderedSet 的集合代理对象 NSKeyValueOrderedSet if ([NSStringFromClass -

本文详细解析了Key-Value Coding (KVC)在iOS开发中的核心概念、使用方法、异常处理及自定义KVC实现。通过实例演示了如何在对象间转换字典与模型,以及处理非对象类型值和集合操作。
:基本使用 && 底层原理&spm=1001.2101.3001.5002&articleId=118556195&d=1&t=3&u=7be98ad73faa48acac46bc6eb0a595b0)
3577

被折叠的 条评论
为什么被折叠?



