KVC
的全称是Key-Value Coding
(键值编码),是由NSKeyValueCoding
非正式协议启用的一种机制,对象采用这种机制来提供对其属性的间接访问,能够经过字符串来访问一个对象的成员变量或其关联的存取方法(getter or setter
)。KVC
间接访问对象的属性,而且KVC
还能够访问私有变量。某些状况下,KVC
还能够帮助简化代码。KVC
是许多其余 Cocoa 技术的基础概念,好比 KVO、Cocoa bindings、Core Data、AppleScript-ability 等等。- (nullable id)valueForKey:(NSString *)key; // 经过 key 来取值
- (nullable id)valueForKeyPath:(NSString *)keyPath; // 经过 keyPath 来取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; // 经过 key 来赋值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 经过 keyPath 来赋值
复制代码
以下是 BankAccount 类的声明:html
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
复制代码
对于 BankAccount 的实例对象myAccount
。 咱们可使用setter
方法为currentBalance
属性赋值,这是直接的,但缺少灵活性。面试
[myAccount setCurrentBalance:@(100.0)];
复制代码
咱们也能够经过KVC
间接为currentBalance
属性赋值,经过其键Key
设置值。编程
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
复制代码
KVC
还支持多级访问,KeyPath
用法跟点语法相同。 例如:咱们想对myAccount
的owner
属性的address
属性的street
属性赋值,其KeyPath
为owner.address.street
。数组
[myAccount setValue:@"地址" forKeyPath:@"owner.address.street"];
复制代码
给定一组Key
,得到一组value
,以字典的形式返回。该方法为数组中的每一个Key
调用valueForKey:
方法。markdown
- (NSDictionary<NSString *,id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
复制代码
将指定字典中的值设置到消息接收者的属性中,使用字典的Key
标识属性。默认实现是为每一个键值对调用setValue:forKey:
方法 ,会根据须要用nil
替换NSNull
对象。app
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues;
复制代码
咱们能够像访问其它对象同样使用valueForKey:
或setValue:forKey:
方法来获取或设置集合对象(主要指NSArray
和NSSet
)。可是,当咱们要操做集合对象的内容,好比添加或者删除元素时,经过KVC
的可变代理方法获取集合代理对象是最有效的。
根据KVO
的实现原理,是在运行时动态生成子类并重写setter
方法来达到能够通知全部观察者对象的目的,所以咱们对集合对象进行操做是不会触发KVO
的。当咱们要使用KVO
监听集合对象变化时,须要经过KVC
的可变代理方法获取集合代理对象,而后对代理对象进行操做。当代理对象的内部对象发生改变时,会触发KVO
的监听方法。
传送门:iOS - 关于 KVO 的一些总结ide
KVC
提供了三种不一样的代理对象访问的代理方法,每种都有Key
和KeyPath
两种方法。oop
mutableArrayValueForKey:
和 mutableArrayValueForKeyPath:
post
返回NSMutableArray
对象的代理对象。性能
mutableSetValueForKey:
和 mutableSetValueForKeyPath:
返回NSMutableSet
对象的代理对象。
mutableOrderedSetValueForKey:
和 mutableOrderedSetValueForKeyPath:
返回NSMutableOrderedSet
对象的代理对象。
KVC
的valueForKeyPath:
方法除了能够取出属性值之外,还能够在KeyPath
中嵌套集合运算符,来对集合对象进行操做。
如下是KeyPath
集合运算符的格式,主要分为 3 个部分。
集合运算符主要分为三类:
NSNumber
实例。NSArray
实例返回。NSArray
或NSSet
实例。以下是 BankAccount 类和 Transaction 类的声明。BankAccount 中有一个 transactions 数组属性,其元素为 Transaction 类型。Transaction 类中定义了 3 个属性,分别为收款人、金额、日期。
@interface BankAccount : NSObject
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many
@end
@interface Transaction : NSObject
@property (nonatomic) NSString* payee; // To whom
@property (nonatomic) NSNumber* amount; // How much
@property (nonatomic) NSDate* date; // When
@end
复制代码
下表是为了演示集合运算符使用而给出的 transactions 数组的数据。
payee | amount | date |
---|---|---|
Green Power | $120.00 | Dec 1, 2015 |
Green Power | $150.00 | Jan 1, 2016 |
Green Power | $170.00 | Feb 1, 2016 |
Car Loan | $250.00 | Jan 15, 2016 |
Car Loan | $250.00 | Feb 15, 2016 |
Car Loan | $250.00 | Mar 15, 2016 |
General Cable | $120.00 | Dec 1, 2015 |
General Cable | $155.00 | Jan 1, 2016 |
General Cable | $120.00 | Feb 1, 2016 |
Mortgage | $1,250.00 | Jan 15, 2016 |
Mortgage | $1,250.00 | Feb 15, 2016 |
Mortgage | $1,250.00 | Mar 15, 2016 |
Animal Hospital | $600.00 | Jul 15, 2016 |
以某种方式合并集合中的对象,并返回右键路径中指定的属性的数据类型匹配的一个对象,通常返回NSNumber
实例。
读取集合中每一个元素的右键路径指定的属性,将其转换为double
类型 (nil
用 0 替代),并计算这些值的算术平均值。而后将结果以NSNumber
实例返回。
// 计算上表中 amount 的平均值。
NSNumber *transactionAverage = [self.transactions valueForKeyPath:@"@avg.amount"];
// transactionAverage 格式化的结果为 $ 456.54。
复制代码
计算集合中的元素个数,以NSNumber
实例返回。
// 计算 transactions 集合中的元素个数。
NSNumber *numberOfTransactions = [self.transactions valueForKeyPath:@"@count"];
// numberOfTransactions 的值为 13。
复制代码
备注:
@count
运算符比较特别,它不须要写右键路径,即便写了也会被忽略。
读取集合中每一个元素的右键路径指定的属性,将其转换为double
类型 (nil
用 0 替代),并计算这些值的总和。而后将结果以NSNumber
实例返回。
// 计算上表中 amount 的总和。
NSNumber *amountSum = [self.transactions valueForKeyPath:@"@sum.amount"];
// amountSum 的结果为 $ 5935.00。
复制代码
返回集合中右键路径指定的属性的最大值。
// 获取日期的最大值。
NSDate *latestDate = [self.transactions valueForKeyPath:@"@max.date"];
// latestDate 的值为 Jul 15, 2016.
复制代码
返回集合中右键路径指定的属性的最小值。
// 获取日期的最小值。
NSDate *earliestDate = [self.transactions valueForKeyPath:@"@min.date"];
// earliestDate 的值为 Dec 1, 2015.
复制代码
备注:
@max
和@min
根据右键路径指定的属性在集合中搜索,搜索使用compare:
方法进行比较,许多基础类 (如NSNumber类
) 中都有定义。所以,右键路径指定的属性必须能响应compare:
消息。搜索忽略值为nil
的集合项。能够经过重写compare:
方法对搜索过程进行控制。
根据运算符的条件,将符合条件的对象以一个NSArray
实例返回。
读取数组中每一个元素的右键路径指定的属性,放在一个NSArray
实例中并返回。
// 获取集合中的全部 payee 对象。
NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];
// payees 数组包含如下字符串:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital。
复制代码
读取数组中每一个元素的右键路径指定的属性,放在一个NSArray
实例中,将数组进行去重后返回。
// 获取集合中的全部不一样的 payee 对象。
NSArray *distinctPayees = [self.transactions valueForKeyPath:@"@distinctUnionOfObjects.payee"];
// distinctPayees 数组包含如下字符串:Car Loan, General Cable, Animal Hospital, Green Power, Mortgage。
复制代码
注意: 在使用数组运算符时,若是有任何操做的对象为
nil
,则valueForKeyPath:
方法将引起异常。
处理集合对象中嵌套其余集合对象的状况,并根据运算符返回一个NSArray
或NSSet
实例。
以下 moreTransactions 是装着 transaction 对象的数组,arrayOfArrays 数组中嵌套了 self.transactions 和 moreTransactions 两个数组。
NSArray* moreTransactions = @[<# transaction data #>];
NSArray* arrayOfArrays = @[self.transactions, moreTransactions];
复制代码
下表是 moreTransactions 数组的数据。
payee | amount | date |
---|---|---|
General Cable - Cottage | $120.00 | Dec 18, 2015 |
General Cable - Cottage | $155.00 | Jan 9, 2016 |
General Cable - Cottage | $120.00 | Dec 1, 2016 |
Second Mortgage | $1,250.00 | Nov 15, 2016 |
Second Mortgage | $1,250.00 | Sep 20, 2016 |
Second Mortgage | $1,250.00 | Feb 12, 2016 |
Hobby Shop | $600.00 | Jun 14, 2016 |
读取集合中的每一个集合中的每一个元素的右键路径指定的属性,放在一个NSArray
实例中并返回。
// 获取 arrayOfArrays 集合中的每一个集合中的全部 payee 对象。
NSArray *collectedPayees = [arrayOfArrays valueForKeyPath:@"@unionOfArrays.payee"];
// collectedPayees 数组包含如下字符串:Green Power, Green Power, Green Power, Car Loan, Car Loan, Car Loan, General Cable, General Cable, General Cable, Mortgage, Mortgage, Mortgage, Animal Hospital, General Cable - Cottage, General Cable - Cottage, General Cable - Cottage, Second Mortgage, Second Mortgage, Second Mortgage, Hobby Shop.
复制代码
读取集合中的每一个集合中的每一个元素的右键路径指定的属性,放在一个NSArray
实例中,将数组进行去重后返回。
// 获取 arrayOfArrays 集合中的每一个集合中的全部不一样的 payee 对象。
NSArray *collectedDistinctPayees = [arrayOfArrays valueForKeyPath:@"@distinctUnionOfArrays.payee"];
// collectedDistinctPayees 数组包含如下字符串:Hobby Shop, Mortgage, Animal Hospital, Second Mortgage, Car Loan, General Cable - Cottage, General Cable, Green Power.
复制代码
读取集合中的每一个集合中的每一个元素的右键路径指定的属性,放在一个NSSet
实例中,去重后返回。
NSSet *collectedDistinctPayees = [setOfSets valueForKeyPath:@"@distinctUnionOfSets.payee"];
复制代码
注意:
- 在使用嵌套运算符时,
valueForKeyPath:
内部会根据运算符建立一个NSMutableArray
或NSMutableSet
对象,将集合中的array
和set
添加进去再进行操做。若是集合中有非集合元素,会致使Crash
。- 使用
unionOfArrays
或distinctUnionOfArrays
运算符,消息接收者应该是arrayOfArrays
类型,即NSArray< NSArray* >* arrayOfArrays;
;使用distinctUnionOfSets
运算符,消息接收者应该是setOfSets
或者arrayOfSets
类型。不然会发生异常。- 在使用嵌套运算符时,若是有任何操做的对象为
nil
, 则valueForKeyPath:
方法将引起异常。
若是集合中的对象都是NSNumber
,右键路径能够用self
。
NSArray *array = @[@1, @2, @3, @4, @5];
NSNumber *sum = [array valueForKeyPath:@"@sum.self"];
NSLog(@"%d",[sum intValue]);
复制代码
上面介绍了KVC
为咱们提供的集合运算符,咱们能不能自定义呢?
咱们使用Runtime
打印NSArray
类的方法列表:
- (void)printNSArrayMethods
{
u_int count;
Method *methods = class_copyMethodList([NSArray class], &count);
for (int i = 0; i < count ; i++)
{
Method method = methods[i];
SEL sel = method_getName(method);
NSLog(@"%d---%@", i, NSStringFromSelector(sel));
}
free(methods);
}
复制代码
0---mr_isEqualToOutputDevicesArray:
1---mr_containsAnyOf:
2---mr_map:
3---sg_enumerateChunksOfSize:usingBlock:
4---_pas_mappedArrayWithTransform:
5---_pas_shuffledArrayUsingRng:
......
复制代码
方法不少,咱们搜索关键字avg
、count
、sum
等KVC
为咱们提供的集合运算符,发现都有对应的方法_<operatorKey>ForKeyPath:
。
267---_avgForKeyPath:
268---_countForKeyPath:
264---_sumForKeyPath:
269---_maxForKeyPath:
270---_minForKeyPath:
266---_unionOfObjectsForKeyPath:
273---_distinctUnionOfObjectsForKeyPath:
265---_unionOfArraysForKeyPath:
272---_distinctUnionOfArraysForKeyPath:
274---_distinctUnionOfSetsForKeyPath:
复制代码
注意: 咱们再来看一下
NSSet
类支持哪些集合运算符:50---_sumForKeyPath: 51---_avgForKeyPath: 52---_countForKeyPath: 53---_maxForKeyPath: 54---_minForKeyPath: 55---_distinctUnionOfArraysForKeyPath: 56---_distinctUnionOfObjectsForKeyPath: 57---_distinctUnionOfSetsForKeyPath: 复制代码
可见
NSSet
类不支持@unionOfObjects
和@unionOfArrays
运算符,若是使用了就会抛出异常NSInvalidArgumentException
并致使程序崩溃,reason:[<__NSSetI 0x6000017a12f0> valueForKeyPath:]: this class does not implement the unionOfArrays operation.
不支持该运算符。而
NSArray
类虽然支持@distinctUnionOfSets
运算符,但其必须是arrayOfSets
类型,即NSArray< NSSet* >* arrayOfSets;
。由于_distinctUnionOfSetsForKeyPath
方法中会建立一个NSMutableSet
实例,并调用unionSet:
方法将集合中的set
的元素添加进去再进行操做。若是是arrayOfArrays
类型就会抛出异常NSInvalidArgumentException
并致使程序崩溃,reason:'*** -[NSMutableSet unionSet:]: set argument is not an NSSet'
即集合中有非NSSet
元素。
咱们尝试为NSArray
添加一个分类,并定义一个_medianForKeyPath:
方法,用来获取NSArray
中的中位数。
#import <Foundation/Foundation.h>
@interface NSArray (HTOperator)
- (NSNumber *)_medianForKeyPath:(NSString *)keyPath;
@end
#import "NSArray+HTOperator.h"
@implementation NSArray (HTOperator)
- (NSNumber *)_medianForKeyPath:(NSString *)keyPath {
//排序
NSArray *sortedArray = [self sortedArrayUsingSelector:@selector(compare:)];
double median;
if (self.count % 2 == 0) {
NSInteger index1 = sortedArray.count * 0.5;
NSInteger index2 = sortedArray.count * 0.5 - 1;
median = ([[sortedArray objectAtIndex:index1] doubleValue] + [[sortedArray objectAtIndex:index2] doubleValue]) * 0.5;
} else {
NSInteger index = (sortedArray.count-1) * 0.5;
median = [[sortedArray objectAtIndex:index] doubleValue];
}
return [NSNumber numberWithDouble:median];
}
复制代码
测试。
NSArray *array = @[@9, @7, @8, @2, @6, @3];
NSNumber *num = [array valueForKeyPath:@"@median.self"];
NSLog(@"%f",[num doubleValue]);
// 6.500000
复制代码
KVC
支持基础数据类型和结构体,在使用KVC
进行赋值或取值的时候,会自动在非对象值和对象值之间进行转换。
valueForKey:
时,若是返回值非对象,会使用该值初始化一个NSNumber
(用于基础数据类型)或NSValue
(用于结构体)实例,而后返回该实例。setValue:forKey:
时,若是key
的数据类型非对象,则会发送一条<type>Value
消息给value
对象以提取基础数据,而后赋值给key
。注意:
- 由于
Swift
中的全部属性都是对象,因此这里仅适用于Objective-C
属性。- 当进行赋值如
setValue:forKey:
时,若是key
的数据类型是非对象类型,则value
就禁止传nil
。不然会调用setNilValueForKey:
方法,该方法的默认实现抛出异常NSInvalidArgumentException
,并致使程序Crash
。
下表是KVC
对于基础数据类型和NSNumber
对象之间的转换。
Data type | Creation method | Accessor method |
---|---|---|
BOOL | numberWithBool: | boolValue (in iOS) charValue (in macOS)* |
char | numberWithChar: | charValue |
double | numberWithDouble: | doubleValue |
float | numberWithFloat: | floatValue |
int | numberWithInt: | intValue |
long | numberWithLong: | longValue |
long long | numberWithLongLong: | longLongValue |
short | numberWithShort: | shortValue |
unsigned char | numberWithUnsignedChar: | unsignedChar |
unsigned int | numberWithUnsignedInt: | unsignedInt |
unsigned long | numberWithUnsignedLong: | unsignedLong |
unsigned long long | numberWithUnsignedLongLong: | unsignedLongLong |
unsigned short | numberWithUnsignedShort: | unsignedShort |
下表是KVC
对于结构体类型和NSValue
对象之间的转换。
Data type | Creation method | Accessor method |
---|---|---|
CGPoint | valueWithCGPoint: | CGPointValue |
CGRect | valueWithCGRect: | CGRectValue |
CGSize | valueWithCGSize: | CGSizeValue |
NSRange | valueWithRange: | rangeValue |
除了以上CGPoint
、CGRect
、CGSize
和NSRange
类型的结构体能够和NSValue
对象之间进行转换,咱们自定义的结构体也能够包装成NSValue
对象,示例以下。
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
复制代码
// 取值
NSValue* result = [myClass valueForKey:@"threeFloats"];
复制代码
// 赋值
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
复制代码
KVC
提供了属性验证的方法,以下。咱们能够在使用KVC
赋值前验证可否为这个key
赋值指定value
。 validateValue
方法的默认实现是查看消息接收者类中是否实现了遵循命名规则为validate<Key>:error:
的方法,若是有的话就返回调用该方法的结果;若是没有的话,则默认验证成功并返回YES
。咱们能够在消息接收者类中实现validate<Key>:error:
的方法来自定义逻辑返回YES
或NO
。
- (BOOL)validateValue:(id _Nullable *)value
forKey:(NSString *)key
error:(NSError * _Nullable *)error;
- (BOOL)validateValue:(inout id _Nullable *)ioValue
forKeyPath:(NSString *)inKeyPath
error:(out NSError * _Nullable *)outError;
复制代码
示例
在Person
类中实现了validateName:error:
方法,验证给name
赋的值是否是jack
。
// ViewController.m
Person *person = [[Person alloc] init];
NSString *value = @"rose";
NSString *key = @"name";
NSError *error;
BOOL result = [person validateValue:&value forKey:key error:&error];
if (error) {
NSLog(@"error = %@", error);
return;
}
NSLog(@"%d",result);
// Person.m
- (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError
{
NSString *name = *value;
BOOL result = NO;
if ([name isEqualToString:@"jack"]) {
result = YES;
}
return result;
}
// 打印:0
复制代码
备注: 默认状况下,
KVC
是不会自动验证属性的。
除了了解KVC
的使用,了解KVC
取值和赋值过程的工做原理也是颇有必要的。
如下是valueForKey:
方法的默认实现,给定一个key
做为输入参数,在消息接收者类中操做,执行如下过程。
get<Key>
、<key>
、is<Key>
、_<key>
顺序查找方法。 countOf<Key>
、objectIn<Key>AtIndex:
、<key>AtIndexes:
命名的方法。 NSArray
的方法的集合代理对象(类型为NSKeyValueArray
,继承自NSArray
),并返回该对象。不然执行③;
NSArray
消息转换为countOf<Key>
、objectIn<Key>AtIndex:
、<Key>AtIndexes:
消息的组合,并将其发送给KVC
调用方。若是原始对象还实现了一个名为get<Key>:range:
的可选方法,则代理对象也会在适当时使用该方法。KVC
调用方与代理对象一块儿工做时,容许底层属性的行为如同NSArray
同样,即便它不是NSArray
。countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
命名的方法。 NSSet
的方法的集合代理对象(类型为NSKeyValueSet
,继承自NSSet
),并返回该对象。不然执行④;
NSSet
消息转换为countOf<Key>
、enumeratorOf<Key>
、memberOf<Key>:
消息的组合,并将其发送给KVC
调用方。KVC
调用方与代理对象一块儿工做时,容许底层属性的行为如同NSSet
同样,即便它不是NSSet
。+accessInstanceVariablesDirectly
方法的返回值(默认返回YES
)。若是返回YES
,就按照_<key>
、_is<Key>
、<key>
、is<Key>
顺序查找成员变量。若是找到就直接取值并执行⑤,不然执行⑥。若是+accessInstanceVariablesDirectly
方法返回NO
也执行⑥。NSNumber
支持的数据类型,则将其存储在NSNumber
实例并返回。NSNumber
支持的数据类型,则转换为NSValue
对象, 而后返回。valueForUndefinedKey:
方法,该方法抛出异常NSUnknownKeyException
,并致使程序Crash
。这是默认实现,咱们能够重写该方法根据特定key
作一些特殊处理。如下是setValue:forKey:
方法的默认实现,给定key
和value
做为输入参数,尝试将KVC
调用方的属性名为key
的值设置为value
,执行如下过程。
set<Key>:
、_set<Key>:
顺序查找方法。 value
传进去(根据须要进行数据类型转换),不然执行②。+accessInstanceVariablesDirectly
方法的返回值(默认返回YES
)。若是返回YES
,就按照_<key>
、_is<Key>
、<key>
、is<Key>
顺序查找成员变量(同 基本的 Getter 搜索模式)。若是找到就将value
赋值给它(根据须要进行数据类型转换),不然执行③。若是+accessInstanceVariablesDirectly
方法返回NO
也执行③。setValue:forUndefinedKey:
方法,该方法抛出异常NSUnknownKeyException
,并致使程序Crash
。这是默认实现,咱们能够重写该方法根据特定key
作一些特殊处理。如下是mutableArrayValueForKey:
方法的默认实现,给定一个key
做为输入参数,返回属性名为key
的集合的代理对象(这里指NSMutableArray
对象),在消息接收者类中操做,执行如下过程。
① 查找一对方法insertObject:in<Key>AtIndex:
和removeObjectFrom<Key>AtIndex:
(至关于NSMutableArray
的原始方法insertObject:atIndex:
和removeObjectAtIndex:
),
或者insert<Key>:atIndexes:
和remove<Key>AtIndexes:
(至关于NSMutableArray
的原始方法insertObjects:atIndexes:
和removeObjectsAtIndexes:
)。
insertion
方法和一个removal
方法,则返回一个代理对象,来响应发送给NSMutableArray
的消息,经过发送insertObject:in<Key>AtIndex:
、removeObjectFrom<Key>AtIndex:
、insert<Key>:atIndexes:
、remove<Key>AtIndexes:
组合消息给KVC
调用方。不然执行②。该代理对象类型为
NSKeyValueFastMutableArray2
,继承链为NSKeyValueFastMutableArray2
->NSKeyValueFastMutableArray
->NSKeyValueMutableArray
->NSMutableArray
。
replace object
方法,如replaceObjectIn<Key>AtIndex:withObject:
或replace<Key>AtIndexes:with<Key>:
,代理对象在适当的状况下也会使用它们,以得到最佳性能。② 查找set<Key>:
方法。
若是找到,就会向KVC
调用方发送一个set<Key>:
消息,来返回一个响应NSMutableArray
消息的代理对象。不然执行③。
该代理对象类型为
NSKeyValueSlowMutableArray
,继承链为NSKeyValueSlowMutableArray
->NSKeyValueMutableArray
->NSMutableArray
。
注意: 此步骤中描述的机制比上一步的效率低得多,由于它可能重复建立新的集合对象,而不是修改现有的集合对象。所以,在设计本身的键值编码兼容对象时,一般应该避免使用它。
给代理对象发送
NSMutableArray
消息都会调用set<Key>:
方法。即,对代理对象进行修改,都是调用set<Key>:
来从新赋值,因此效率会低不少。
③ 查看消息接收者类的+accessInstanceVariablesDirectly
方法的返回值(默认返回YES
)。若是返回YES
,就按照_<key>
、<key>
顺序查找成员变量。若是找到就返回一个代理对象,该代理对象将接收全部NSMutableArray
消息,一般是NSMutableArray
或其子类。不然执行④。若是+accessInstanceVariablesDirectly
方法返回NO
也执行④。
④ 返回一个可变的集合代理对象。当它接收到NSMutableArray
消息时,发送一个valueForUndefinedKey:
消息给KVC
调用方,该方法抛出异常NSUnknownKeyException
,并致使程序Crash
。这是默认实现,咱们能够重写该方法根据特定key
作一些特殊处理。
除了以上三种,KVC
还有NSMutableSet
和NSMutableOrderedSet
两种搜索模式,它们的搜索规则和NSMutableArray
相同,只是搜索和调用的方法不一样。具体能够查看KVC
官方文档 KVC - Accessor Search Patterns。
KVC
搜索规则,当没有搜索到对应的key
或者keyPath
相关方法或者变量时,会调用对应的异常方法valueForUndefinedKey:
或setValue:forUndefinedKey:
,这两个方法的默认实现是抛出异常NSUnknownKeyException
,并致使程序Crash
。咱们能够重写这两个方法来处理异常。- (nullable id)valueForUndefinedKey:(NSString *)key;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
复制代码
setValue:forKey:
时,若是key
的数据类型是非对象类型,则value
就禁止传nil
。不然会调用setNilValueForKey:
方法,该方法的默认实现是抛出异常NSInvalidArgumentException
,并致使程序Crash
。咱们能够重写这个方法来处理异常。- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"hidden"]) {
[self setValue:@(NO) forKey:@”hidden”];
} else {
[super setNilValueForKey:key];
}
}
复制代码
会,经过KVC
修改为员变量值也会触发KVO
。
valueForKey:
和setValue:forKey:
这里面的key
是没有任何限制的,当咱们知道一个类或实例它内部的私有变量名称的状况下,咱们在外界能够经过已知的key
来对它的私有变量进行访问或者赋值的操做,从这个角度来说KVC
键值编码技术会违背面向对象的编程思想。