1、介紹:
KVC鍵值編碼在iOS中允許開發者通過 Key 直接訪問對象的屬性,或者給對象的屬性或者成員變量賦值,而不需要調用setter和getter方法。這樣就可以在運行時動態在訪問和修改對象的屬性。
? 作為可以稱為基類的NSObject有一個分類NSKeyValueCoding, KVC的定義都是對NSObject的擴展來實現的,所以對于所有繼承了NSObject在類型,都能使用KVC。
下面就該類中屬性方法做分析,同時配合一些例子來展示KVC的功能與實際運用。
2、屬性方法分析:
(1.)
@property (class, readonly) BOOL accessInstanceVariablesDirectly;
該屬性用來判斷是否能直接訪問實例變量,而且是readonly屬性的,但在實際運用時實際上是重寫類方法 + (BOOL)accessInstanceVariablesDirectly;
該類方法默認返回YES,如果設置為NO,則只會對指定的key進行精準搜索(e.g: key 為 @"name",則只會對名字為@"name"的屬性進行搜索匹配,不會在搜索@"name"無結果的情況下再去對包含name字符的一些其他屬性或成員變量或者方法進行搜索),若沒有結果則直接進入異常方法:取值異常方法 --> - (nullable id)valueForUndefinedKey:(NSString *)key;
或者 賦值異常方法 --> - (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
(2.)
- (nullable id)valueForKey:(NSString *)key;
直接通過key來取值。蘋果源碼中對該方法的描述使用了大量的篇幅(這里就不附加英文描述了,太長了~??),這些描述主要解釋了當調用- (nullable id)valueForKey:(NSString *)key;
方法時KVC在內部是按什么樣的順序來尋找key的。
實現查找機制如下:
一、首先按
get<Key>
,<key>
,is<Key>
的順序方法查找getter方法,如果找到這樣的方法的話會直接調用。同時如果返回值是對象類型則直接返回該對象,如果是BOOL或者int等基礎數據類型, 會做NSNumber轉換,否則其他的任意類型將被轉換成NSValue類型返回(現在不僅限于NSPoint, NRange, NSRect, and NSSize這四個結構體類型);二、如果通過上面的方法沒有找到的話,KVC會查找與
-countOf<Key>
,-indexIn<Key>OfObject:
,-objectIn<Key>AtIndex:
,-<key>AtIndexes:
這些方法名匹配的方法。如果找到-countOf<Key>
,-indexIn<Key>OfObject:
這兩個方法和剩余其他兩個方法中的一個(至少三個同時存在,同時前兩個還是必須的!如果這四個同時存在則會優先搜索-objectIn<Key>AtIndex:
方法并返回依照這一方法檢索的結果,結果是一個包含三個元素的NSOrderedSet對象),那么就會返回一個可以響應NSOrderedSet(有序集合)所有方法的代理集合NSKeyValueOrderedSet,調用這個代理集合的方法,或者說給這個代理集合發送NSOrderedSet的方法,就會以countOf<Key>
,-indexIn<Key>OfObject:
,objectIn<Key>AtIndex
,<Key>AtIndex
這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:
。如果還實現了一個可選的方法-get<Ket>:range:
方法, 為了最佳的性能該方法將在適當的時候被調用。三、如果返回有序集合的方法沒有找到,KVC會查找與
-countOf<Key>
,-objectIn<Key>AtIndex:
,-<key>AtIndexes:
(對應-[NSArray objectsAtIndexes:]方法) 這些方法名匹配的方法。如果找到-countOf<Key>
這個方法和剩余其他兩個方法中的一個,那么就會返回一個可以響應NSArray所有方法的代理集合NSKeyValueArray,調用這個代理集合的方法,或者說給這個代理集合發送NSArray的方法,就會以countOf<Key>
,objectIn<Key>AtIndex
,<Key>AtIndexs
這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:
。如果還實現了一個可選的方法-get<Key>:range:
方法, 為了最佳的性能該方法將在適當的時候被調用。* 在這里一定要區別開和第二步順序的區別!!!*四、如果返回數組的方法沒有找到,KVC會查找與
-countOf<Key>
,-enumeratorOf<Key>
,and -memberOf<Key>:
(對應被NSSet類定義的方法) 這些方法名匹配的方法。如果這三個方法全部找到,那么就會返回一個可以響應NSSet所有方法的代理集合NSKeyValueSet,調用這個代理集合的方法,或者說給這個代理集合發送NSSet的方法,就會以-countOf<Key>
,-enumeratorOf<Key>
,and -memberOf<Key>:
這幾個方法組合的形式調用,消息被發送給原始接收者-valueForKey:
。* 在這里要區別開和第二步順序及第三步順序的區別!!!*五、如果以上getter方法和集合的訪問方法均未找到,那么會再檢查類方法
+ (BOOL)accessInstanceVariablesDirectly;
,如果返回YES(默認行為),會按_<key>
,_is<Key>
,<key>
,is<Key>
的順序搜索成員變量名,如果找到這樣的實例變量那么返回規則與第一步的返回值順序是一樣的。如果返回是NO,那么會直接調用- (nullable id)valueForUndefinedKey:(NSString *)key;
方法。六、若通過以上方法均為找到,直接調用
- (nullable id)valueForUndefinedKey:(NSString *)key;
方法。
看了這么多的說明是不是有點懵??,下面我們用代碼來解釋這一切~:
步驟一示例:
Person * person = [[Person alloc] init];
NSString * personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
//下圖中是Person.m中代碼
可以看到搜索到-getName方法有結果后就不會再查找了。
但是下面有一種情況:
可以看到這里打印了null!不是說好的按順序查找的么?而且下面我也定義了-valueForUndefinedKey:
方法(沒有在圖片中粘出來)同樣沒有走,這是為什么呢?因為我定義了名字為name的屬性!當你定義了屬性后,KVC不會去搜索-is<Key>
方法!
?其實只要有確定的key屬性,則只會檢索兩個方法: -get<Key>
,-<key>
,如果這兩個方法沒有結果,則直接放回null。
以下情況全是在沒有key屬性的情況下進行的操作(再次強調以下:在沒有key屬性的情況下搜索步驟2,3,4,5,6才有可能發生!!!,否則只會搜索步驟1中的-get<Key>
,-<key>
這兩個方法)
步驟二示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
//下圖中是Person.m中代碼
由上面兩圖可以看到:返回一個包含三個元素的NSOrderedSet對象,且元素均相同,其中控制元素個數的是-countOf<Key>方法
!!示例代碼-2_1中結果元素均為@77,示例代碼-2_2中結果只與-<key>AtIndexes:
方法返回值的第一個元素有關系!
步驟三示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
這里同步驟二最大的區別就是沒有-indexIn<Key>OfObject:
方法,若有這一方法絕不會搜索步驟3,4,5的步驟。
步驟四示例:
Person * person = [[Person alloc] init];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
這里必須這三個方法全部存在才可以!
步驟五示例:
Person * person = [[Person alloc] init];
[person setValueForAllMemberVariable];
id personName = [person valueForKey:@"name"];
NSLog(@"%@", personName);
NSLog(@"%@", NSStringFromClass([personName class]));
這里就不一一的貼圖展示效果了,有興趣的自己可以測試一下??。
至于步驟六,相信大多數人都遇到過~~
(3.)
- (void)setValue:(nullable id)value forKey:(NSString *)key;
直接通過key為對象屬性或者成員變量賦值。我們直接看KVC在內部是按什么樣的順序來尋找key的然后為對應屬性和成員變量賦值的。
實現機制如下:
- 一、程序會優先調用
-set<Key>:
屬性值方法(setter方法),代碼通過setter方法完成設置。這時候如果value參數是 非對象類型 (這再次強調一下** 非對象類型!!**),則不允許將參數設為nil,否則就會調用- (void)setNilValueForKey:(NSString *)key
方法,造成崩潰,為了防止出現這種情況可以重寫該方法,然后針對出現的nil做一些其他操作。如果value參數是基本數據類型(int,float 等)要先轉化為NANumber類型或者NAValue類型。
e.g:
Person * person = [[Person alloc] init];
[person setValue:nil forKey:@"name"];
[person setValue:nil forKey:@"age"];
- 二、如果沒有找到
-set<Key>:
方法(即不存在屬性key),KVC機制會檢查+ (BOOL)accessInstanceVariablesDirectly
方法有沒有返回YES,默認該方法會返回YES,如果你重寫了該方法讓其返回NO的話,那么在這一步KVC會執行-setValue:forUndefinedKey:
方法,當返回為YES時,所以KVC機制會按照_<key>
,_is<Key>
,<key>
,is<Key>
順序搜索該類里面有沒有這些名稱的成員變量,無論該變量是在類接口部分定義,還是在類實現部分定義,也無論用了什么樣的訪問修飾符,只在存在這樣的成員變量都可以對該成員變量賦值。
e.g:
- 三、如果以上都沒有找到就會調用
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
這一方法。一般重寫防止異常。
(4、)
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
這一方法主要是在使用KVC賦值前驗證key與Value是否匹配的方法。同時這個方法系統不會自動去檢測,需要自己調用去檢測。默認返回YES。e.g:
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
if ([inKey isEqualToString:@"name"]) {
if ([*ioValue isKindOfClass:[NSString class]]) {
NSLog(@"yes == %d,ioValueClass == %@", YES, NSStringFromClass([*ioValue class]));
return YES;
}else{
NSLog(@"no == %d,ioValueClass == %@", NO, NSStringFromClass([*ioValue class]));
return NO;
}
}
return YES;
}
(5、)
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
使用該方法獲取一個可變集合(NSMutableArray),如果調用該方法則搜索順序如下:
一、搜索
-insertObject:in<Key>AtIndex:
,-removeObjectFrom<Key>AtIndex:
或者-insert<Key>AdIndexes
,- remove<Key>AtIndexes
格式的方法。至少有一個insert方法和一個remove方法,那么才會返回一個可以響應NSMutableArray
所有方法代理集合(類名是NSKeyValueFastMutableArray2
),那么給這個代理集合發送NSMutableArray的方法,會以-insertObject:in<Key>AtIndex:
,-removeObjectFrom<Key>AtIndex:
或-insert<Key>AdIndexes
,-remove<Key>AtIndexes
組合的形式調用。除此之外還有兩個可選實現的接口:-replaceOnjectAtIndex:withObject:
,-replace<Key>AtIndexes:with<Key>:
。二、如果沒有找到,則搜索
-set<Key>:
格式的方法,如果找到,那么發送給代理集合的NSMutableArray最終都會調用set<Key>:方法。 也就是說,-mutableArrayValueForKey:
取出的代理集合修改后,會用用-set<Key>:
重新賦值回去。若沒有找到,則后面和調用- (void)setValue:(nullable id)value forKey:(NSString *)key;
方法的搜索步驟相同。
要特別強調的是:當你單獨調用該方法時,是沒有任何效果的,必須與 NSMutableArray 的 添加/插入元素方法或者移除元素方法結合使用才會進行搜索~,所以使用該方法就會產生一個問題:這一方法調用既包含了取值搜索也包含了賦值搜索,所以在使用時如果key不是屬性或者相關成員變量,或者未開辟內存空間,則必然會拋出異常!
常用場景:KVO監聽可變集合類屬性時,如果直接操作可變集合(例如[self.xxxArray remove···]
)時,不會觸發KVO,但是通過[self mutableArrayValueForKey:key] remove···]
則會觸發KVO。
e.g:
(6、)
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key ;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
這兩個方法與 (5、) 中的運用方式基本相同,只是第一步檢索判斷的方法不同,所以在此不多贅述。有興趣的可以看一下官方文檔。
(7、)
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
這一部分方法的應用場景一般是,一個類的成員變量有可能是其他的自定義類,那么通過key的KVC方法一層一層的去獲取值或者賦值,就會比較繁瑣,keyPath就是解決這問題的。
e.g:
// Person.h
@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
@end
// Country.h
@interface Country : NSObject
@property (nonatomic,strong) Person *person;
@end
使用keyPath要用點 . 去分割不同的key!其實質原理還是key方法的查找順序!
(8、)
// - valueForKey: 方法查找不到key時會調用,默認拋出NSUndefinedKeyException異常
- (nullable id)valueForUndefinedKey:(NSString *)key;
// -setValue:forKey: 方法查找不到key時會調用,默認拋出NSUndefinedKeyException異常
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
// -setValue:forKey:方法的key為基本類型時傳入的value為nil時調用。
- (void)setNilValueForKey:(NSString *)key;
這三個方法其實前面介紹其他方法時都已運用過了,大家可以自己實踐一下,印象會更深刻些??????。
(9、)
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
對于這兩個方法相信大家都不會陌生,前者是輸入一組key,查找key一一對應的屬性value,然后返回key和value組成的字典。主要用于模型轉字典!后者是輸入一個字典,根據字典的key一一對應類的屬性為其賦值。主要用于字典轉模型!在這里就不舉例說明了~~
2、KVC中的函數操作:
(1、)簡單集合運算符:
?共有:@avg
(平均值)、@sum
(和)、@max
(最大值)、@min
(最小值)@count
(對象總數)(只能用在集合對象中(NSArray,NSSet),對象屬性必須為基本數據類型,返回值均為NSNumber類型);
(2、)對象運算符
?共有@distinctUnionOfObjects
(返回的元素都是唯一的,是去重以后的結果)、@unionOfObjects
(返回的是所有的結果--全集)。它們的返回值都是NSArray。
(3、)數組和集合運算符
? @distinctUnionOfArrays
、@unionOfArrays
、@distinctUnionOfSets
,主要用于集合中包含集合的情況。