KVC
屬於Foundation
框架,不開源,咱們只能經過官方文檔來了解它html
Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.數組
KVC(鍵值編碼
)由 NSKeyValueCoding非正式協議
啓用的一種機制,採用該協議能夠間接訪問
對象的屬性。當一個對象與鍵值編碼兼容時,它的屬性能夠經過一個簡潔、統一的消息傳遞接口經過字符串參數尋址。這種間接訪問機制補充了實例變量及其相關訪問器方法提供的直接訪問。bash
Objects typically adopt key-value coding when they inherit from NSObject (directly or indirectly),服務器
全部直接或者間接繼承了NSObject的類型,也就是幾乎全部的Objective-C對象都能使用KVC (一些純Swift類和結構體是不支持KVC的)app
KVC經常使用的四個方法框架
// 經過 key 設值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
// 經過 key 取值
- (nullable id)valueForKey:(NSString *)key;
// 經過 keyPath 設值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
// 經過 keyPath 取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
複製代碼
NSKeyValueCoding類別中還有其餘的一些方法ide
// 默認返回YES,表示若是沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜索成員,設置成NO就不這樣搜索
+ (BOOL)accessInstanceVariablesDirectly;
// KVC提供屬性值正確性驗證的API,它能夠用來檢查set的值是否正確、爲不正確的值作一個替換值或者拒絕設置新值並返回錯誤緣由。
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
// 這是集合操做的API,裏面還有一系列這樣的API,若是屬性是一個NSMutableArray,那麼能夠用這個方法來返回。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
// 若是Key不存在,且沒有KVC沒法搜索到任何和Key有關的字段或者屬性,則會調用這個方法,默認是拋出異常。
- (nullable id)valueForUndefinedKey:(NSString *)key; // 取值
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key; // 設值
// 若是你在SetValue方法時面給Value傳nil,則會調用這個方法
- (void)setNilValueForKey:(NSString *)key;
// 輸入一組key,返回該組key對應的Value,再轉成字典返回,用於將Model轉到字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
複製代碼
這段內容比較基礎,只須要注意:只有繼承於NSObject的數據才能使用KVC,非NSObject類型的須要作類型轉換。學習
經過 valueForKey:
和 setValue:Forkey
:來間接的獲取和設置屬性值ui
valueForKey
: - Returns the value of a property named by the key parameter. If the property named by the key cannot be found according to the rules described in Accessor Search Patterns, then the object sends itself a valueForUndefinedKey: message. The default implementation of valueForUndefinedKey: raises an NSUndefinedKeyException, but subclasses may override this behavior and handle the situation more gracefully.this
valueForKey
: 返回由 key 參數命名的屬性的值。若是根據訪問者搜索模式中的規則找不到由 key 命名的屬性,則該對象將向自身發送 valueForUndefinedKey:
消息。valueForUndefinedKey:
的默認實現會拋出 NSUndefinedKeyException
異常,可是子類能夠重寫此行爲並更優雅地處理這種狀況。
setValue:forKey:
: Sets the value of the specified key relative to the object receiving the message to the given value. The default implementation of setValue:forKey: automatically unwraps NSNumber and NSValue objects that represent scalars and structs and assigns them to the property. See Representing Non-Object Values for details on the wrapping and unwrapping semantics. If the specified key corresponds to a property that the object receiving the setter call does not have, the object sends itself a setValue:forUndefinedKey: message. The default implementation of setValue:forUndefinedKey: raises an NSUndefinedKeyException. However, subclasses may override this method to handle the request in a custom manner.
setValue:forKey:
: 將該消息接收者的指定 key 的值設置爲給定值。默認實現會自動把表示標量
和結構體
的 NSNumber 和 NSValue 對象解包而後賦值給屬性。若是指定 key 所對應的屬性沒有對應的 setter
實現,則該對象將向自身發送 setValue:forUndefinedKey:
消息,valueForUndefinedKey:
的默認實現會拋出一個 NSUndefinedKeyException
的異常。可是子類能夠重寫此方法以自定義方式處理請求。Example:
AKPerson *person = [[AKPerson alloc] init];
[person setValue:@"akironer" forKey:@"name"];
NSLog(@"%@", [person valueForKey:@"name"]);
打印輸出:akironer
複製代碼
valueForKeyPath:
- Returns the value for the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key—that is, for which the default implementation of valueForKey: cannot find an accessor method—receives a valueForUndefinedKey: message.
valueForKeyPath:
: 返回於接受者的指定key path
上的值。key path
路徑序列中不符合特定鍵的鍵值編碼的任何對象,都會接收到 valueForUndefinedKey:
消息。
setValue:forKeyPath:
- Sets the given value at the specified key path relative to the receiver. Any object in the key path sequence that is not key-value coding compliant for a particular key receives a setValue:forUndefinedKey: message.
setValue:forKeyPath:
: 將該消息接收者的指定 key path
的值設置爲給定值。key path
路徑序列中不符合特定鍵的鍵值編碼的任何對象都將收到setValue:forUndefinedKey:
消息Example:
AKTeacher *teacher = [[AKTeacher alloc] init];
teacher.subject = @"iOS";
person.teacher = teacher;
[person setValue:@"iOS進階之路" forKeyPath:@"teacher.subject"];
NSLog(@"%@",[person valueForKeyPath:@"teacher.subject"]);
打印輸出:iOS進階之路
複製代碼
-> dictionaryWithValuesForKeys:
- Returns the values for an array of keys relative to the receiver. The method calls valueForKey: for each key in the array. The returned NSDictionary contains values for all the keys in the array.
key
數組的值。該方法會爲數組中的每一個 key
調用valueForKey:
。 返回的 NSDictionary
包含數組中全部鍵的值。
setValuesForKeysWithDictionary:
- Sets the properties of the receiver with the values in the specified dictionary, using the dictionary keys to identify the properties. The default implementation invokes setValue:forKey: for each key-value pair, substituting nil for NSNull objects as required.
setValuesForKeysWithDictionary:
:使用字典鍵標識屬性,將指定字典中的對應值設置成該消息接收者的屬性值。默認實現會對每個鍵值對調用 setValue:forKey:
。設置時須要將 nil
替換成 NSNull
Collection objects, such as NSArray, NSSet, and NSDictionary, can’t contain nil as a value. Instead, you represent nil values using the NSNull object.
NSArray
NSSet
和 NSDictionary
等集合對象不能包含 nil
做爲值, 可使用 NSNull對象
代替 nil
值。[person setValuesForKeysWithDictionary:@{@"name": @"akironer", @"age": @(18)}, @"hobby":[NSNULL null]];
NSLog(@"%@", [person dictionaryWithValuesForKeys:@[@"name", @"age"]]);
打印輸出:
{
age = 18;
name = akironer;
hobby = null;
}
複製代碼
// 方法一:普通方式
person.array = @[@"1",@"2",@"3"];
NSArray *array = [person valueForKey:@"array"]; // 不可不數組沒法直接修改,用 array 的值建立一個新的數組
array = @[@"100",@"2",@"3"];
[person setValue:array forKey:@"array"];
NSLog(@"方法一:%@",[person valueForKey:@"array"]);
// 方法二:KVC 的方式
NSMutableArray *ma = [person mutableArrayValueForKey:@"array"];
ma[0] = @"100";
NSLog(@"方法二:%@",[person valueForKey:@"array"]);
打印輸出:
方法一:(
100,
2,
3
)
方法二:(
100,
2,
)
複製代碼
操做集合對象內部的元素來講,更高效的方式是使用 KVC 提供的可變代理方法
。KVC 爲咱們提供了三種不一樣的可變代理方法:
mutableArrayValueForKey:
& mutableArrayValueForKeyPath:
:返回的代理對象表現爲一個 NSMutableArray 對象mutableSetValueForKey:
& mutableSetValueForKeyPath:
:返回的代理對象表現爲一個 NSMutableSet 對象mutableOrderedSetValueForKey:
& mutableOrderedSetValueForKeyPath:
:返回的代理對象表現爲一個 NSMutableOrderedSet 對象在使用 valueForKeyPath:
的時候,可使用集合運算符來實現一些高效的運算操做。
@avg
: 返回操做對象指定屬性的平均值@count
: 返回操做對象指定屬性的個數@max
: 返回操做對象指定屬性的最大值@min
: 返回操做對象指定屬性的最小值@sum
: 返回操做對象指定屬性值之和@distinctUnionOfObjects
: 返回操做對象指定屬性的集合--去重@unionOfObjects
: 返回操做對象指定屬性的集合@distinctUnionOfArrays
: 返回操做對象(嵌套集合)指定屬性的集合--去重,返回的是 NSArray@unionOfArrays
: 返回操做對象(集合)指定屬性的集合@distinctUnionOfSets
: 返回操做對象(嵌套集合)指定屬性的集合--去重,返回的是 NSSet非對象屬性分爲兩類:
經常使用的基本數據類型須要在設置屬性的時候包裝成 NSNumber
對象
除了 NSPoint
NSRange
NSRect
和 NSSize
,對於自定義的結構體,也須要進行 NSValue
的轉換操做.
typedef struct {
float x, y, z;
} ThreeFloats;
// 設值
ThreeFloats floats = {1., 2., 3.};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSValue *reslut = [person valueForKey:@"threeFloats"];
NSLog(@"%@",reslut);
// 取值
ThreeFloats result;
[reslut getValue:&result] ;
NSLog(@"%f - %f - %f",result.x, result.y, result.z);
打印輸出:
{length = 12, bytes = 0x0000803f0000004000004040}
1.000000 - 2.000000 - 3.000000
複製代碼
在學習KVC的搜索規則前,要先弄明白一個屬性的做用,這個屬性在搜索過程當中起到很重要的做用。這個屬性表示是否容許讀取實例變量的值,若是爲YES則在KVC查找的過程當中,從內存中讀取屬性實例變量的值。
@property (class, readonly) BOOL accessInstanceVariablesDirectly;
複製代碼
valueForKey:
方法的默認實現:valueForKey:
方法會在調用者傳入 key
以後會在對象中按下列的步驟進行模式搜索:
get<Key>
<key>
is<Key>
以及 _<key>
的順序查找對象中是否有對應的方法。簡單getter方法
方法,則查找是否有 countOf<Key> 方法
objectIn<Key>AtIndex: 方法
(對應於 NSArray類
定義的原始方法) 以及 <key>AtIndexes: 方法
(對應於 NSArray 方法 objectsAtIndexes:)countOf<Key>
),再找到其餘兩個中的至少一個,則建立一個響應全部 NSArray
方法的代理集合對象,並返回該對象。(翻譯過來就是要麼是 countOf<Key> + objectIn<Key>AtIndex:
,要麼是countOf<Key> + <key>AtIndexes:
,要麼是 countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:
)簡單NSArray方法
,查找名爲 countOf<Key>
enumeratorOf<Key>
memberOf<Key>
這三個方法(對應於NSSet類
定義的原始方法)NSSet
方法的代理集合對象,並返回該對象accessInstanceVariablesDirectly
結果_<key>
_is<Key>
<key>
is<Key>
的順序查找成員變量。若是找到了,將成員變量帶上跳轉到第 5 步,若是沒有找到則跳轉到第 6 步valueForUndefinedKey:
, 默認狀況下拋出NSUndefinedKeyException
異常,可是繼承於NSObject的子類能夠重寫該方法避免崩潰並作相應措施set<Key>:
_set<Key>
順序查找對象中是否有對應的方法accessInstanceVariablesDirectly
結果_<key>
_is<Key>
<key>
is<Key>
的順序查找成員變量,找到了就賦值;找不到就跳轉第3步setValue:forUndefinedKey:
。默認狀況下拋出NSUndefinedKeyException
異常,可是繼承於NSObject的子類能夠重寫該方法避免崩潰並作出相應措施這裏再明確下實例變量、成員變量、屬性之間的區別:
咱們不去重寫屬性的 getter 和 setter 方法以及聲明對應的實例變量,那麼編譯器就會幫咱們作這件事,那麼是否是說有多少個屬性,就會生成多少個對應的 getter 和 setter 呢?
顯然,編譯器不會這麼傻。編譯器在objc-accessors.mm
中運用通用原則
給全部屬性都提供了同一的入口,setter方法會根據修飾符不一樣調用不一樣方法,最後統一調用reallySetProperty
方法示。
KVC在iOS開發中是毫不可少的利器,也是許多iOS開發黑魔法的基礎。列舉一下KVC的使用場景。
最基本的用法,相信你們都很屬性了
對於類裏的私有屬性,Objective-C是沒法直接訪問的,可是KVC是能夠的。
運用了KVC和Objc的runtime組合的技巧,完成模型和字典的相互轉換
在 iOS 13 以前,咱們能夠經過 KVC 去獲取和設置系統的私有屬性,但從 iOS 13 以後,這種方式被禁用掉了。相信很多同窗適配 iOS 13的時候,已經遇到了KVC的訪問限制問題。
例如UITextField中的placeHolderText已經不能修改了,這裏提供兩種簡答的修改思路,想要深刻了解的能夠參考關於iOS 13 中KVC 訪問限制的一些處理
attributedPlaceholder
屬性修改Placeholder
顏色NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"請輸入佔位文字" attributes: @{NSForegroundColorAttributeName:[UIColor redColor], NSFontAttributeName:textField.font }];
textField.attributedPlaceholder = attrString;
複製代碼
UITextField
從新寫一個方法- (void)resetTextField: (UITextField *)textField
{
Ivar ivar = class_getInstanceVariable([textField class], "_placeholderLabel");
UILabel *placeholderLabel = object_getIvar(textField, ivar);
placeholderLabel.text = title;
placeholderLabel.textColor = color;
placeholderLabel.font = [UIFont systemFontOfSize:fontSize];
placeholderLabel.textAlignment = alignment;
}
複製代碼
在設值時設置空值,能夠經過重寫setNilValueForKey
來監聽
In the default implementation, when you attempt to set a non-object property to a nil value, the key-value coding compliant object sends itself a setNilValueForKey: message. The default implementation of setNilValueForKey: raises an NSInvalidArgumentException, but an object may override this behavior to substitute a default value or a marker value instead, as described in Handling Non-Object Values.
在默認實現中,當您試圖將非對象屬性
設置爲nil
時,KVC的對象會向本身發送一條setNilValueForKey:
消息。setNilValueForKey
的默認實現會引起NSInvalidArgumentException,但對象能夠重寫此行爲以替換默認值或標記值。
Given that an invocation of -setValue:forKey: would be unable to set the keyed value because the type of the parameter of the corresponding accessor method is an NSNumber scalar type or NSValue structure type but the value is nil, set the keyed value using some other mechanism.
大致意思就是說:只對NSNumber
或者NSValue
類型的數據賦空值時,setNilValueForKey
纔會觸發。下面的例子中,subject不會觸發
@implementation LGPerson
- (void)setNilValueForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
NSLog(@"你傻不傻: 設置 %@ 是空值",key);
return 0;
}
[super setNilValueForKey:key];
}
@ end
[person setValue:nil forKey:@"age"];
[person setValue:nil forKey:@"subject"]; // subject不觸發 - 官方註釋裏面說只對 NSNumber - NSValue
複製代碼
對於未定義的key, 能夠經過重寫setValue:forUndefinedKey:
、valueForUndefinedKey:
來監聽。
例如:
咱們在字典轉模型的時候,例如服務器返回一個id字段,可是對於客戶端來講id是系統保留字段,能夠重寫setValue:forUndefinedKey:
方法並在內部處理id參數的賦值。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"id"]) {
self.userId = [value integerValue];
}
}
複製代碼
在調用KVC時能夠先進行驗證,驗證經過下面兩個方法進行,支持key
和keyPath
兩種方式。
驗證方法須要咱們手動調用,並不會在進行KVC的過程當中自動調用
- (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;
複製代碼
該方法的工做原理:
下面是使用驗證方法的例子。在validateValue
方法的內部實現中,若是傳入的value或key有問題,能夠經過返回NO來表示錯誤,並設置NSError對象。
AKPerson* person = [[AKPerson alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}
複製代碼
NSKeyValueCoding
隱式協議所提供的機制。valueForKey:
和 valueForKeyPath:
來取值,不考慮集合類型的話具體的取值過程以下:get<Key>
<key>
is<Key>
_<key>
的順序查找方法accessInstanceVariablesDirectly
判斷是否能讀取成員變量來返回屬性值_<key>
_is<Key>
<key>
is<Key>
的順序查找成員變量setValueForKey:
和 setValueForKeyPath:
來取值,不考慮集合類型的話具體的設置值過程以下:set<Key>
_set<Key>
的順序查找方法_<key>
_is<Key>
<key>
is<Key>
的順序查找成員變量此次咱們依據蘋果的官方文檔完成了KVC的探索,其實蘋果的英文註釋和官方文檔寫的很是用心,咱們在探索 iOS 底層的時候,文檔思惟十分重要,多閱讀文檔總會有新的收穫。