前言
一、概述
KVC全稱是Key Value Coding(鍵值編碼),是可以通過物件屬性名稱(Key)直接對屬性值(value)編碼(coding)“編碼”可以理解為“賦值及訪問”。而不需要呼叫明確的存取方法。這樣就可以在執行時動態在訪問和修改物件的屬性,而不是在編譯時確定。
KVC的優勢是在沒有訪問器(setter、getter)方法的類中,此時點語法無法使用。
KVC提供了一種間接訪問其屬性方法或成員變數的機制,可以通過字串來訪問對應的屬性方法或成員變數。
二、使用
KVC的定義都是對NSObject的擴充套件來實現的,Objective-c中有個顯式的NSKeyValueCoding類別名,所以對於所有繼承了NSObject在型別,都能使用KVC。
在 Swift 中處理 KVC和 Objective-C 中還是有些細微的差別。比如,Objective-C 中所有的類都繼承自 NSObject,而 Swift 中卻不是,所以我們在 Swift 中需要顯式的宣告繼承自 NSObject。
因為 Swift 中的 Optional 機制,所以 valueForKey 方法返回的是一個 Optional 值,我們還需要對返回值做一次解包處理,才能得到實際的屬性值。
1、基本使用
下面是KVC中最常用的幾個方法:
//直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key;
//通過Key來設值
- (void)setValue:(nullable id)value forKey:(NSString *)key;
//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath;
//通過KeyPath來設值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
複製程式碼
舉例:Book類擁有書名name屬性以及author,且author類擁有一個address屬性。
@interface Author : NSObject
@property(strong, nonatomic) NSString* address;
@end
@implementation Author
@end
@interface Book : NSObject
@property(strong, nonatomic) NSString* name;// 書名
@property(strong, nonatomic) Author* author;// 作者
@end
@implementation Book
@end
複製程式碼
使用時:
// ViewController.m
#import "ViewController.h"
#import "Book.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Book *book = [[Book alloc]init];
[book setValue:@"Hello world" forKey:@"name"]; // 設定值
NSLog(@"age=%@",[book valueForKey:@"name"]); // 取值
// keyPath 方式
[book setValue:@"ShangHai" forKeyPath:@"author.address"]; // 設定值
NSLog(@"author.address=%@",[book valueForKeyPath:@"author.address"]);//取值
}
@end
複製程式碼
2、底層執行機制
- [object setValue:@”value” forKey:@”property”]
-
存取器(setter方法)匹配:先尋找與setKey同名的方法。找到直接賦值。[self setProperty:@”value”]。
-
例項變數匹配:尋找與key,_isKey,_key,isKey同名的例項變數,直接賦值。property = value或_property = value等。
-
找不到,就會直接報錯 setValue:forUndefinedKey:報找不到的錯誤。
如果我們想讓這個類禁用KVC,那麼重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可,這樣的話如果KVC沒有找到set:屬性名時,會直接用setValue:forUNdefinedKey:方法。
+ (BOOL)accessInstanceVariablesDirectly;
//預設返回YES,表示如果沒有找到Set<Key>方法的話,會按照_key,_iskey,key,iskey的順序搜尋成員,設定成NO就不這樣搜尋
複製程式碼
- [object valueForKey:@”property”]
-
訪問器(getter方法)匹配:先尋找與key,isKey, getKey (實測還有_key)同名的方法。
-
例項變數匹配:尋找與key, _key,isKey,_isKey同名的例項變數。
-
如果還沒有找到的話,呼叫valueForUndefinedKey:。
若value值為BOOL或Int等值型別時,KVC可以自動的將數值或結構體型的資料打包或解包成NSNumber或NSValue物件,以達到適配的目的。
Ps:swift中使用keyPath的方式,但是僅支援struct
struct Book {
var name:String
}
var book = Book(name: "Swift")
// set
book[keyPath: name] = "swift4"
// get
let valueOfName = book[keyPath:name]
複製程式碼
3、鍵值驗證
在實際開發中我們獲取對一些Key的value值有一些特殊的要求。KVC為我們提供了驗證Key對應的Value是否可用的方法:
- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;
複製程式碼
這為我們提供了一次糾錯的機會。但是,KVC是不會自動呼叫鍵值驗證方法的,就是說我們如果想要鍵值驗證則需要手動驗證。也就是說,需要自己需要驗證的類中重寫-(BOOL)-validate:error:,預設返回Yes。
舉例:
@implementation Book
- (BOOL)validateName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{
NSString* name = *value;
name = name.capitalizedString;
if ([name isEqualToString:@"Not-name"]) {
return NO;
}
return YES;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
Book *book = [[Book alloc]init];
NSError* error;
NSString *value = @"Not-name"; // result 為 NO
//NSString *value = @"BookName"; // result 為 YES
BOOL result = [book validateValue:&value forKey:@"name" error:&error];
if (result) {
NSLog(@"OK");
} else {
NSLog(@"NO");
}
}
複製程式碼
4、不存在的key及nil值處理
- 處理不存在的key值
我們可以考慮重寫setValue: forUndefinedKey:方法與valueForUndefinedKey:方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"您設定的key:[%@]不存在", key);
NSLog(@"您設定的value為:[%@]", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
NSLog(@"您訪問的key:[%@]不存在", key);
return nil;
}
複製程式碼
- value為nil的處理
當程式嘗試為某個屬性設定nil值時,如果該屬性並不接受nil值,那麼程式將會自動執行該物件的setNilValueForKey:方法。我們同樣可以重寫這個方法:
- (void)setNilValueForKey:(NSString *)key {
//對不能接受nil的屬性進行處理
if ([key isEqualToString:@"price"]) {
//對應你具體的業務來處理
price = 0;
}else {
[super setNilValueForKey:key];
}
}
複製程式碼
5、一些函式操作
KVC同時還提供了一些較複雜的函式,主要有下面這些:
- 集合運算子
目前總共有五個函式:@avg, @count , @max , @min ,@sum5
- 物件運算子
共有兩個:@distinctUnionOfObjects 及 @unionOfObjects
它們的返回值都是NSArray。兩者的區別在於:前者會去除返回值中重複的資料,後者則將資料全部返回。
舉例:
@interface Book : NSObject
@property(assign, nonatomic) NSInteger price;
@end
@implementation Book
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Book *book1 = [Book new];
book1.price = 40;
Book *book2 = [Book new];
book2.price = 20;
Book *book3 = [Book new];
book3.price = 30;
Book *book4 = [Book new];
book4.price = 10;
// 價格重複的
Book *book5 = [Book new];
book5.price = 10;
NSLog(@"----------集合運算子----------");
NSArray* arr = @[book1,book2,book3,book4];
NSNumber* sum = [arr valueForKeyPath:@"@sum.price"];
NSLog(@"sum:%f",sum.floatValue);
NSNumber* avg = [arr valueForKeyPath:@"@avg.price"];
NSLog(@"avg:%f",avg.floatValue);
NSNumber* count = [arr valueForKeyPath:@"@count"];
NSLog(@"count:%f",count.floatValue);
NSNumber* min = [arr valueForKeyPath:@"@min.price"];
NSLog(@"min:%f",min.floatValue);
NSNumber* max = [arr valueForKeyPath:@"@max.price"];
NSLog(@"max:%f",max.floatValue);
NSLog(@"----------物件操作符----------");
NSArray* arrBooks = @[book1,book2,book3,book4, book5];
NSLog(@"distinctUnionOfObjects");
NSArray* arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
for (NSNumber *price in arrDistinct) {
NSLog(@"%f",price.floatValue);
}
NSLog(@"unionOfObjects");
NSArray* arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
for (NSNumber *price in arrUnion) {
NSLog(@"%f",price.floatValue);
}
}
@end
---輸出結果:---
----------集合運算子----------
sum:100.000000
avg:25.000000
count:4.000000
min:10.000000
max:40.000000
----------物件操作符----------
distinctUnionOfObjects
10.000000
20.000000
30.000000
40.000000
unionOfObjects
40.000000
20.000000
30.000000
10.000000
10.000000
複製程式碼
三、實際應用
1、訪問私有變數
對於類裡的私有屬性,Objective-C是無法直接訪問的,但是KVC是可以的。
@interface Book : NSObject
{
NSString * owner;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Book *book = [Book new];
// 這種訪問方式,會直接報錯。因為owner是私有屬性。
// book.owner = @"tao";
// 利用KVC訪問到私有變數
[book setValue:@"tao" forKey:@"owner"];
NSLog([book valueForKey:@"owner"]); // 輸出 tao
}
@end
複製程式碼
2、修改一些控制元件的內部屬性
在開發中,我們常常需要對一些控制元件的某些屬性做修改,但是很多UI控制元件都由很多內部UI控制元件組合而成的,系統並沒有提供這訪問這些控制元件的API,這樣我們就無法正常地訪問和修改這些控制元件的樣式。但是,KVC可以幫我們解決大部分這種型別的問題。
3、結合Runtime打造字典轉model
可以利用KVC和執行時將字典轉換為模型。 具體程式碼可以我的Github上檢視。
四、總結
上面的幾點就是本人對KVC學習的一些記錄,如有不妥之處,請大家多多指正。關於KVC更多更詳細的資料,大家可以去到官方文件Key-Value Coding Programming Guide檢視學習。