蘋果官網地址
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簡介
KVC的全稱是Key-Value Coding
,俗稱“鍵值編碼”,可以通過一個key來訪問某個屬性。常見的API有:
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通過keyPath可以設置屬性的屬性
- (void)setValue:(id)value forKey:(NSString *)key;//通過key設置自己的屬性
- (id)valueForKeyPath:(NSString *)keyPath;//通過keyPath訪問屬性的屬性
- (id)valueForKey:(NSString *)key; //通過key訪問自己的屬性
二、賦值:-setValue:forKey:
2.1規則:
- 先查找setter方法,
set<Key>
、_set<Key>
; - 未找到,且
+(BOOL)accessInstanceVariablesDirectly
返回YES(默認為YES),則查找實例變量; - 查找
_<key>
,_is<Key>
,<key>
, oris<Key>
,查到即執行 - 拋出異常,也可以執行
-setValue:forUndefinedKey:
處理異常
2.2不同數據類型的使用
新建一個工程,添加一個類Animal.h
typedef struct {
float a;
float b;
float c;
}ThreeFloats;
@interface Animal : NSObject
@property (nonatomic,copy) NSString *nickname;
@property (nonatomic,assign) int age;
@property (nonatomic,strong) NSArray *friends;
@property (nonatomic,assign) ThreeFloats floats;
@end
在viewcontroller里面
- (void)viewDidLoad {
[super viewDidLoad];
Animal *animal = [Animal alloc];
//1、基本對象類型
[animal setValue:@"dog" forKey:@"nickname"];
NSLog(@"nickname:%@", animal.nickname);
//2、集合類型
[animal setValue:@[@"Jack", @"Rose"] forKey:@"friends"];
NSLog(@"friends:%@", animal.friends);
NSMutableArray *tmpFriends = [animal mutableArrayValueForKey:@"friends"];
tmpFriends[1] = @"Lili";
NSLog(@"friends:%@", animal.friends);
//3、 NSNumber支持的基礎類型
[animal setValue:@3 forKey:@"age"];
NSLog(@"age:%d", animal.age);
//4、 其他類型,除上述類型外,其他類型要包裝為NSValue進行賦值。
ThreeFloats threeFloats = {1.1, 2.2, 3.3};
NSValue *value = [NSValue valueWithBytes:&threeFloats
objCType:@encode(ThreeFloats)];
[animal setValue:value forKey:@"floats"];
NSValue *floatsValue = [animal valueForKey:@"floats"];
ThreeFloats floats;
[floatsValue getValue:&floats];
NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
}
三、取值:valueForKey:
2.1規則:
看下圖,也可以到蘋果官網查看,文章開頭有鏈接
- 先查找該類中是否有直接的getter,如果有則調用getter,執行第5步;
- 沒有直接的getter,查找類中是否有上述NSArray塊中的方法,如果實現
第一個
和后面兩個中的一個或兩個
方法,則返回一個集合對象,這個集合對象可以響應所有NSArray的方法 - 查找實例是否實現了上述NSSet塊中的3個方法,如果有,則返回一個集合,這個集合可以響應NSSet的所有方法。
- 檢查類方法
accessInstanceVariablesDirectly
如果返回YES則查找對象的實例變量是否匹配下列各式:_<key>->_is<Key>-><key>->is<Key>,如果匹配,執行第5步,否則執行第6步 - 如果屬性類型是對象則直接返回;如果屬性類型是被NSNumber支持的類型,則返回一個NSNumber對象;否則返回一個NSValue對象。
- 拋出異常,也可以執行
- (nullable id)valueForUndefinedKey:(NSString *)key
處理異常
2.2不同數據類型的使用
在上面代碼的基礎上修改,在Animal.h
- (NSInteger)countOfNames {
return _friends.count;
}
- (id)objectInNamesAtIndex:(NSUInteger)index {
return _friends[index];
}
-(NSArray *)namesAtIndexes:(NSIndexSet *)indexes{
return [_friends objectsAtIndexes:indexes];
}
- (nullable id)valueForUndefinedKey:(NSString *)key{
return @"UndefinedKey";
}
在viewcontroller里面
Animal *animal = [[Animal alloc] init];
//1、基本對象
animal.nickname = @"Jack";
NSString *nickname = [animal valueForKey:@"nickname"];
NSLog(@"nickname: %@", nickname);
//2、數組類型的使用,通過-valueForKey:取值時
// 需要實現-countOf<Key>和-objectIn<Key>AtIndex:兩個方法,我們以names為key,來獲取friends屬性:
animal.friends = @[@"Jack", @"Rose"];
NSArray *names = [animal valueForKey:@"names"];
NSLog(@"names: %@", names);
//3、NSNumber支持的基本類型
animal.age = 3;
NSNumber *number = [animal valueForKey:@"age"];
NSLog(@"age: %d", [number intValue]);
//4、NSValue類型
ThreeFloats threeFloats = {1.1, 2.2, 3.3};
animal.floats = threeFloats;
NSValue *floatsValue = [animal valueForKey:@"floats"];
ThreeFloats floats;
[floatsValue getValue:&floats];
NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
四、KVC模式匹配的順序及驗證
4.1 setter和getter方法
添加代碼
@interface Animal () {
NSString *_nickname;
}
@end
@implementation Animal
#pragma mark - setter
// set<Key>
//- (void)setNickname:(NSString *)nickname {
// printf("%s\n", __func__);
// _nickname = nickname;
//}
// _set<Key>
- (void)_setNickname:(NSString *)nickname {
printf("%s\n", __func__);
_nickname = nickname;
}
// setIs<Key>
- (void)setIsNickname:(NSString *)nickname {
printf("%s\n", __func__);
_nickname = nickname;
}
#pragma mark - getter
//// get<Key>
//- (NSString *)getNickname {
// printf("%s\n", __func__);
// return _nickname;
//}
// <key>
- (NSString *)nickname {
printf("%s\n", __func__);
return _nickname;
}
// _<key>
- (NSString *)_nickname {
printf("%s\n", __func__);
return _nickname;
}
// is<Key>
- (NSString *)isNickname {
printf("%s\n", __func__);
return _nickname;
}
@end
viewController相關代碼
Animal *animal = [[Animal alloc] init];
[animal setValue:@"Jack" forKey:@"nickname"];
NSLog(@"\nnickname: %@", [animal valueForKey:@"nickname"]);
這里不在一一測試,可自行注釋方法來驗證
4.2 實例變量
@interface Animal : NSObject{
@public
//NSString *_nickname;
NSString *nickname;
NSString *_isNickname;
NSString *isNickname;
}
@end
@implementation Animal
@end
Animal *animal = [[Animal alloc] init];
[animal setValue:@"Jack" forKey:@"nickname"];
//NSLog(@"_nickname: %@", animal->_nickname);
NSLog(@"nickname: %@", animal->nickname);
NSLog(@"_isNickname: %@", animal->_isNickname);
NSLog(@"isNickname: %@", animal->isNickname);
打印結果:
2020-04-10 11:23:20.867793+0800 kvc[31577:17258752] nickname: (null)
2020-04-10 11:23:20.868476+0800 kvc[31577:17258752] _isNickname: Jack
2020-04-10 11:23:20.868991+0800 kvc[31577:17258752] isNickname: (null)
4.3 + (BOOL)accessInstanceVariablesDirectly
方法
添加代碼
@implementation Animal
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
@end
再次運行,拋出異常:'NSUnknownKeyException', reason: '[<Animal 0x6000015fb5c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nickname.'
五、kvc特殊異常用法
5.1 自動類型轉換
當賦值對象是int
、bool
等基本類型時,賦值NSString
,取值會自動轉換為對象類型。如下,當age賦值為NSString
,取值時對應類型時__NSCFNumber
。
//@property (nonatomic, assign) int age;
[person setValue:@"20" forKey:@"age"]; // int - string
NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);
5.2 設置空值
賦值nil
,當方法參數類型為NSNumber
或者NSValue
時,可以重寫setNilValueForKey
方法重定向。
5.3
設值或者取值找不到key
,也可以重寫對應的方法setValue: forUndefinedKey:
、valueForUndefinedKey
重定向,這個上面有說過。
5.4 鍵值驗證
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError{
if([inKey isEqualToString:@"name"]){
[self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:inKey];
return YES;
}
*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的屬性",inKey,self] code:10088 userInfo:nil];
return NO;
}
我們可以用這個方法,進行容錯
、派發
、消息轉發
等操作。