KVC是由NSKeyValueCoding非正式協議啓用的一種機制,對象採用該協議提供對其屬性的間接訪問。當對象符合鍵值編碼時,其屬性可以通過簡潔、統一的消息傳遞接口通過字符串參數進行尋址。這種間接訪問機制補充了實例變量及其相關訪問器方法提供的直接訪問。鍵值編碼兼容對象提供了一個簡單的消息傳遞接口,該接口在其所有屬性中都是一致的。鍵值編碼是許多其他Cocoa技術的基礎概念,如鍵值觀察、Cocoa綁定、核心數據和AppleScript功能。在某些情況下,鍵值編碼也有助於簡化代碼。
1.KVC鍵值查找流程
1.1.KVC-setValue:(id)value forKey:(NSString *)key的查找流程如下圖:
簡單來說,會依次查找-(void)setKey:(id)value
,-(void)_setKey:(id)value
,-(void)_setIsKey:(id)value
這3個實例方法,如果存在則執行相應的方法,如果不存在則判斷accessInstanceVariablesDirectly
是否等於YES
。如果爲真則會依次查找成員_key
,_isKey
,key
,isKey
,如果存在則使用runtime進行賦值,否則或accessInstanceVariablesDirectly
爲假,則看是否實現了-(void)setValue:(id)value forUndefinedKey:(NSString *)key
方法,未實現則拋出異常。
1.2.KVC-valueForKey:(NSString *)key的查找流程如下圖:
簡單來說,會依次查找-(id)getKey
,-(id)key
,-(void)isKey
,-(void)_key
這4個實例方法,如果存在則執行相應的方法,如果不存在則判斷accessInstanceVariablesDirectly
是否等於YES
。如果爲真則會依次查找成員_key
,_isKey
,key
,isKey
,如果存在則使用runtime進行取值,否則或accessInstanceVariablesDirectly
爲假,則看是否實現了-(id)valueForUndefinedKey:(NSString *)key
方法,未實現則拋出異常。
以上的存取規則在
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
和- (id)valueForKeyPath:(NSString *)keyPath
中同樣適用。
2.KVC對結構體類型的存取
定義一個結構體類型 NXBookstruct
,和一個類類型NXBookclass
。
typedef struct {
NSString *name;
NSInteger priceValue;
}NXBookstruct;
@interface NXBookclass : NSObject
@property (nonatomic) NXBookstruct bookstruct;
@end
@implementation NXBookclass
@end
那麼採用KVO的方式如何設置和讀取結構體類型屬性的值呢?
NXBookclass *book = [[NXBookclass alloc] init];
{
NXBookstruct bookstruct = {@"Chinese", 28};
NSValue *value = [NSValue valueWithBytes:&bookstruct objCType:@encode(NXBookstruct)];
[bookclass setValue:value forKey:@"bookstruct"];
NSLog(@"通過類屬性直接訪問bookstruct:name=%@, priceValue=%@", bookclass.bookstruct.name, @(bookclass.bookstruct.priceValue));
//通過類屬性直接訪問bookstruct:name=Chinese, priceValue=28
}
{
NXBookstruct bookstruct;
NSValue *value = [bookclass valueForKey:@"bookstruct"];
if (@available(iOS 11.0, *)) {
[value getValue:&bookstruct size:sizeof(NXBookstruct)];
} else {
[value getValue:&bookstruct];
}
NSLog(@"通過KVC間接訪問bookstruct:name=%@, priceValue=%@", bookstruct.name, @(bookstruct.priceValue));
//通過KVC間接訪問bookstruct:name=Chinese, priceValue=28
}
3.KVC操作符方面的運用
集合操作符方面的運用常見列舉如下:
-
@count
:數組中元素的個數 -
@avg.key
:數組中key對應的值的平均值 -
@max.key
:數組中key對應的值的最大值 -
@min.key
:數組中key對應的值的最小值 -
@sum.key
:數組中key對應的值的總和 -
@distinctUnionOfObjects.key
:返回數組中key對應值構成的數組,去除重複 -
@unionOfObjects.key
:返回數組中key對應值構成的數組,不去除重複 -
@distinctUnionOfArrays.key
:返回二維數組中key對應值構成的數組,去除重複 -
@unionOfArrays.key
:返回二維數組中key對應值構成的數組,不去除重複
定義一個class NXBookclass
:
@interface NXBookclass : NSObject
@property (nonatomic) NXBookstruct bookstruct;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger priceValue;
@end
@implementation NXBookclass
@end
執行以下代碼,可以進行驗證:
NSMutableArray <NXBookclass *>*bookclasses = [NSMutableArray arrayWithCapacity:5];
for (NSInteger i = 1; i <= 9; i++){
NXBookclass *bookclass = [[NXBookclass alloc] init];
bookclass.name = @(i).stringValue;
bookclass.priceValue = 10 * i;
if(bookclass.priceValue > 80){
bookclass.priceValue = 80;
}
if(bookclass.priceValue < 20){
bookclass.priceValue = 20;
}
[bookclasses addObject:bookclass];
}
/*bookclasses:20,20,30,40,50,60,70,80,80*/
NSLog(@"@count=%@", [bookclasses valueForKeyPath:@"@count"]);
//@count=9
NSLog(@"@avg.priceValue=%@", [bookclasses valueForKeyPath:@"@avg.priceValue"]);
//@avg.priceValue=50
NSLog(@"@max.priceValue=%@", [bookclasses valueForKeyPath:@"@max.priceValue"]);
//@max.priceValue=80
NSLog(@"@min.priceValue=%@", [bookclasses valueForKeyPath:@"@min.priceValue"]);
//@min.priceValue=20
NSLog(@"@sum.priceValue=%@", [bookclasses valueForKeyPath:@"@sum.priceValue"]);
//@sum.priceValue=450
NSLog(@"@distinctUnionOfObjects.priceValue=%@", [bookclasses valueForKeyPath:@"@distinctUnionOfObjects.priceValue"]);
//@distinctUnionOfObjects.priceValue=(70,40,80,50,20,60,30)
NSLog(@"@unionOfObjects.priceValue=%@", [bookclasses valueForKeyPath:@"@unionOfObjects.priceValue"]);
//@unionOfObjects.priceValue=(20,20,30,40,50,60,70,80,80)
NSMutableArray *unionOfBookclasses = [NSMutableArray arrayWithCapacity:2];
[unionOfBookclasses addObject:bookclasses];
[unionOfBookclasses addObject:bookclasses];
NSLog(@"@distinctUnionOfArrays.priceValue=%@", [unionOfBookclasses valueForKeyPath:@"@distinctUnionOfArrays.priceValue"]);
//@distinctUnionOfArrays.priceValue=(40,80,30,70,20,60,50)
NSLog(@"@unionOfArrays.priceValue=%@", [unionOfBookclasses valueForKeyPath:@"@unionOfArrays.priceValue"]);
//@unionOfArrays.priceValue=(20,20,30,40,50,60,70,80,80,20,20,30,40,50,60,70,80,80)
4.自定義KVC
我們搞清楚了KVC的超着流程之後,就可以按照KVC的原理來自定義一套KVC。
4.1.準備工作
獲取一個類的成員變量列表和方法列表
@interface NXApi : NSObject
/*獲取成員變量列表:cls當前類,forward是否向上查詢父類*/
+ (NSArray *)varList:(Class)cls forward:(BOOL)forward;
/*獲取方法變量列表:cls當前類,forward是否向上查詢父類*/
+ (NSArray *)methodList:(Class)cls forward:(BOOL)forward;
@end
@implementation NXApi
+ (NSArray *)varList:(Class)cls forward:(BOOL)forward{
if(!cls || [cls isEqual:[NSObject class]]){
return @[];
}
NSMutableArray *retValue = [NSMutableArray arrayWithCapacity:2];
unsigned int count;
Ivar * rss = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Ivar rs = rss[i];
NSString *name = [NSString stringWithFormat:@"%s", ivar_getName(rs)];
[retValue addObject:name];
}
free(rss);
Class superclass = [cls superclass];
if(superclass && forward){
[retValue addObjectsFromArray:[NXApi varList:superclass forward:forward]];
}
return retValue;
}
+ (NSArray *)methodList:(Class)cls forward:(BOOL)forward{
if(!cls || [cls isEqual:[NSObject class]]){
return @[];
}
NSMutableArray *retValue = [NSMutableArray arrayWithCapacity:2];
unsigned int count;
Method *rss = class_copyMethodList(cls, &count);
for (unsigned int i = 0; i < count; i++) {
Method rs = rss[i];
NSString *name = [NSString stringWithFormat:@"%s", sel_getName(method_getName(rs))];
[retValue addObject:name];
}
free(rss);
Class superclass = [cls superclass];
if(superclass && forward){
[retValue addObjectsFromArray:[NXApi methodList:superclass forward:forward]];
}
return retValue;
}
@end
4.2 自定義KVC方法
@interface NSObject(NX)
- (void)nx_setValue:(id)value forKey:(NSString *)key;
- (id)nx_valueForKey:(NSString *)key;
@end
@implementation NSObject(NX)
- (void)nx_setValue:(id)value forKey:(NSString *)key{
if (key == nil || key.length == 0) {
return ;
}
NSString *Key = key.capitalizedString;
//1、 set<Key>: / _set<Key>: /setIs<Key>:
NSMutableArray *__selStringArray = [NSMutableArray arrayWithCapacity:3];
[__selStringArray addObject:[NSString stringWithFormat:@"set%@:", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"_set%@:", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"setIs%@:", Key]];
for(NSString *selString in __selStringArray){
SEL sel = NSSelectorFromString(selString);
if(sel && [self respondsToSelector:sel]){
[self performSelector:sel withObject:value];
NSLog(@"set/sel:%@", selString);
return;
}
}
//2、 self.class.accessInstanceVariablesDirectly == YES => _<key>,_is<Key>,<key>, is<Key>
if (self.class.accessInstanceVariablesDirectly) {
//獲取實例變量列表
NSArray *varStringArray = [NXApi varList:self.class forward:YES];
NSMutableArray *__varStringArray = [NSMutableArray arrayWithCapacity:4];
[__varStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
[__varStringArray addObject:[NSString stringWithFormat:@"_is%@", Key]];
[__varStringArray addObject:key];
[__varStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
for (NSString *varString in __varStringArray){
if([varStringArray containsObject:varString]){
Ivar ivar = class_getInstanceVariable(self.class, varString.UTF8String);
object_setIvar(self, ivar, value);
NSLog(@"set/var:%@", varString);
return;
}
}
}
//3、 setValue:forUndefinedKey:
if([[NXApi methodList:self.class forward:YES] containsObject:@"setValue:forUndefinedKey:"]){
[self setValue:value forUndefinedKey:key];
}
//4、Exception
}
- (id)nx_valueForKey:(NSString *)key{
if (key == nil || key.length == 0) {
return [NSNull null];
}
NSString *Key = key.capitalizedString;
//1、 搜索實例的方法 get<Key>, key, isKey, _key
NSMutableArray *__selStringArray = [NSMutableArray arrayWithCapacity:4];
[__selStringArray addObject:[NSString stringWithFormat:@"get%@", Key]];
[__selStringArray addObject:key];
[__selStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
[__selStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
for(NSString *selString in __selStringArray){
SEL sel = NSSelectorFromString(selString);
if(sel && [self respondsToSelector:sel]){
NSLog(@"get/sel:%@", selString);
return [self performSelector:sel];
}
}
// 2、 self.class.accessInstanceVariablesDirectly == YES => _<key>,_is<Key>,<key>, is<Key>
if (self.class.accessInstanceVariablesDirectly) {
//獲取實例變量列表
NSArray *varStringArray = [NXApi varList:self.class forward:YES];
NSMutableArray *__varStringArray = [NSMutableArray arrayWithCapacity:4];
[__varStringArray addObject:[NSString stringWithFormat:@"_%@", key]];
[__varStringArray addObject:[NSString stringWithFormat:@"_is%@", Key]];
[__varStringArray addObject:key];
[__varStringArray addObject:[NSString stringWithFormat:@"is%@", Key]];
for (NSString *varString in __varStringArray){
if([varStringArray containsObject:varString]){
Ivar ivar = class_getInstanceVariable(self.class, varString.UTF8String);
NSLog(@"get/var:%@", varString);
return object_getIvar(self, ivar);
}
}
}
// 3、 valueForUndefinedKey:
// 經過測試只要在子類中實現了valueForUndefinedKey:方法就可以正常調用不會報錯
if([[NXApi methodList:self.class forward:YES] containsObject:@"valueForUndefinedKey:"]){
return [self valueForUndefinedKey:key];
}
// 4. Exception
return nil;
}
@end