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
鍵值編碼技術會違背面向對象的編程思想。