KVC
KVC -- Key Value Coding 鍵值編碼
- 鍵值編碼的基本概念
- 鍵值編碼是一個用于簡介訪問對象屬性的機制敏晤,使用該機制不需要調(diào)用存取方法和變量實例就可以訪問對象屬性
- 鍵值編碼的方法在object-c非正式協(xié)議(類目)NSKeyValueCoding中被聲明嘴脾,默認的實現(xiàn)方法有NSObject提供。
- 鍵值編碼支持帶有對象值得屬性彩倚,同時也支持純數(shù)值類型和結(jié)果扶平。非對象參數(shù)和返回值類型會被識別并自動封裝/解封结澄。
- 設(shè)置和訪問
鍵值編碼中的基本調(diào)用包括-valueForKey: 和 -setValue:forKey:這兩個方法,它們以字符串的形式向?qū)ο蟀l(fā)送消息们妥,字符串是我們關(guān)注屬性的關(guān)鍵监婶。
Person *person = [[Person alloc] init];
[person setValue:@"小明" forKey:@"name"];
NSString *personName = [person valueForKey:@"name"];
/*
[person setValue:@"小明" forKey:@"_name"];
NSString *personName = [person valueForKey:@"_name"];
*/
/*
KVC 首先會去調(diào)用對象的setter齿桃、getter方法短纵,如果setter、getter方法不存在則會查找實例變量名為name的屬性
如果實例變量名為name的屬性不存在會查找實例變量名為_name的屬性鱼冀。如果都不存在會報錯。
*/
打印結(jié)果:
personName = 小明
是否存在setter充易、getter方法蔽氨,如果不存在帆疟,它將在內(nèi)部查找名為_key 或 key的實例變量宇立。通過KVC妈嘹,可以獲取不存在getter方法的對象值,無需通過對象指針直接訪問柬脸。這里我們需要注意倒堕,當(dāng)我們通過-setValue:forKey:設(shè)置對象的值爆价,或通過-valueForKey: 來獲取對象的值時,如若對象的實例變量為基本數(shù)據(jù)類型時(char骤宣、int憔披、float爸吮、BOOL)拗胜,我們需要對數(shù)據(jù)進行封裝。
- 路徑
除了通過鍵值設(shè)置外锈遥,鍵值編碼還支持指定路徑,像文件系統(tǒng)一樣丽惶。[person setValue:@"小明" forKeyPath:@"name"]; NSString *personName = [person valueForKeyPath:@"name"];
先創(chuàng)建Person 和 Dog 這兩個類
Person類
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;
@property(nonatomic,assign) float money;
@property (nonatomic, strong) Dog *dog;
- (void)printTelephone;
/**
* 通過KVC進行字典轉(zhuǎn)模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
@end
#import "Person.h"
#import "Dog.h"
@interface Person ()
// Person 私有屬性
@property (nonatomic, strong) NSString *telephone;
@end
@implementation Person
/**
* 初始化方法
*
* @return <#return value description#>
*/
- (instancetype)init{
self = [super init];
if (self != nil) {
_telephone = @"18200002222";
};
return self;
}
- (void)printTelephone{
NSLog(@"電話為: %@",_telephone);
}
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
@end
Dog類
#import <Foundation/Foundation.h>
@interface Dog : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int price;
@end
#import "Dog.h"
@implementation Dog
@end
使用KVC給Person 和 Dog 的屬性賦值
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person setValue:@"小明" forKey:@"name"];
[person setValue:@"22" forKey:@"age"];
[person setValue:@"1000" forKeyPath:@"money"];
[person setValue:@"旺財" forKeyPath:@"dog.name"];
/*
這種方式也可以給dog的name賦值
[person.dog setValue:@"旺財" forKeyPath:@"name"];
*/
打印結(jié)果:
姓名: 小明
年齡: 22
金錢: 1000.00(傳入的是一個字符串類型@"1000",說明KVC可以自動進行類型轉(zhuǎn)換)
狗名: 旺財
- forKey 和 forKeyPath 的一些差異
- forKeyPath 包含了所有 forKey 的功能
- forKeyPath 可以進行內(nèi)部的點語法,層層訪問內(nèi)部的屬性
```
[person setValue:@"旺財" forKeyPath:@"dog.name"];
```
- key值一定要在屬性中找到,否則會有crash
```
[person setValue:@"小明" forKey:@"name11"];
```
```
*** Terminating app due to uncaught exception 'NSUnknownKeyException',
reason: '[<Person 0x7ff28bd1ca80> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key name11.'
```
可以通過KVC修改類的私有成員變量
Person *person = [[Person alloc] init];
[person printTelephone];
[person setValue:@"15066669999" forKey:@"telephone"];
[person printTelephone];
打印結(jié)果:
//初始化默認號碼
電話為: 18200002222
//修改過之后的號碼
電話為: 15066669999
使用KVC實現(xiàn)字典轉(zhuǎn)模型
Person.h 中聲明部分
/**
* 通過KVC進行字典轉(zhuǎn)模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
Person.m 中實現(xiàn)部分
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
使用過程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年齡: %d",person.age);
NSLog(@"金錢: %.2f",person.money);
NSLog(@"人所擁有的狗: %@",person.dog);
NSLog(@"狗的類型: %@",person.dog.class);
打印結(jié)果:
姓名: 小王
年齡: 33
金錢: 1000.00
人所擁有的狗: {
name = "wang cai";
price = 500;
}
狗的類型: __NSDictionaryI
如果此時要打印狗的信息:name 和 price 會報錯
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年齡: %d",person.age);
NSLog(@"金錢: %.2f",person.money);
NSLog(@"人所擁有的狗: %@",person.dog);
NSLog(@"狗的類型: %@",person.dog.class);
NSLog(@"狗的名字: %@",person.dog.name);
NSLog(@"狗的價格: %d",person.dog.price);
姓名: 小王
年齡: 33
金錢: 1000.00
人所擁有的狗: {
name = "wang cai";
price = 500;
}
狗的類型: __NSDictionaryI
-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20'
*** First throw call stack:
(
0 CoreFoundation 0x000000010796ce65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x00000001073e5deb objc_exception_throw + 48
2 CoreFoundation 0x000000010797548d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00000001078c290a ___forwarding___ + 970
4 CoreFoundation 0x00000001078c24b8 _CF_forwarding_prep_0 + 120
5 ???áaá?§?áêü 0x0000000106ee4df1 -[ViewController test1] + 865
6 ???áaá?§?áêü 0x0000000106ee4469 -[ViewController viewDidLoad] + 73
7 UIKit 0x0000000107eaff98 -[UIViewController loadViewIfRequired] + 1198
8 UIKit 0x0000000107eb02e7 -[UIViewController view] + 27
9 UIKit 0x0000000107d86ab0 -[UIWindow addRootViewControllerViewIfPossible] + 61
10 UIKit 0x0000000107d87199 -[UIWindow _setHidden:forced:] + 282
11 UIKit 0x0000000107d98c2e -[UIWindow makeKeyAndVisible] + 42
12 UIKit 0x0000000107d11663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
13 UIKit 0x0000000107d17cc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760
14 UIKit 0x0000000107d14e7b -[UIApplication workspaceDidEndTransaction:] + 188
15 FrontBoardServices 0x000000010a6e5754 -[FBSSerialQueue _performNext] + 192
16 FrontBoardServices 0x000000010a6e5ac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
17 CoreFoundation 0x0000000107898a31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
18 CoreFoundation 0x000000010788e95c __CFRunLoopDoSources0 + 556
19 CoreFoundation 0x000000010788de13 __CFRunLoopRun + 867
20 CoreFoundation 0x000000010788d828 CFRunLoopRunSpecific + 488
21 UIKit 0x0000000107d147cd -[UIApplication _run] + 402
22 UIKit 0x0000000107d19610 UIApplicationMain + 171
23 ???áaá?§?áêü 0x0000000106ee570f main + 111
24 libdyld.dylib 0x000000010a0a892d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
/*
KVC 賦值是非常暴力的
給person對象狗屬性賦值的時候相當(dāng)于
[person setValue:@{@"name":@"wang cai",
@"price":@"500"} forKey:@"dog"];
直接把@{@"name":@"wang cai",@"price":@"500"}賦值給dog屬性
所以dog得class打印出來是__NSDictionaryI
開發(fā)中不介意使用setValuesForKeysWithDictionary:
1.字典中的key值必須在模型的屬性中找到
2.如果模型中的屬性帶有模型,setValuesForKeysWithDictionary:不能正確轉(zhuǎn)換
應(yīng)用場景:簡單的字典轉(zhuǎn)模型 ----> 框架(MJExtention)
*/
KVC setValuesForKeysWithDictionary:底層實現(xiàn),解決模型中的屬性帶有模型不能正確轉(zhuǎn)換問題
- KVC setValuesForKeysWithDictionary:底層實現(xiàn)
Person.h 中聲明部分
/**
* 通過KVC進行字典轉(zhuǎn)模型
*
* @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;
Person.m 中實現(xiàn)部分
- (void)dictionaryToModel:(NSDictionary *)dic{
// [self setValuesForKeysWithDictionary:dic];
// KVC
// setValuesForKeysWithDictionary:底層實現(xiàn)
// 遍歷字典中的所有key,去模型中查找有沒有對應(yīng)的屬性名着撩,如果就給這個屬性賦值
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[self setValue:obj forKey:key];
NSLog(@"%@ %@",key,obj);
/*
age 33
money 1000
dog {
name = "wang cai";
price = 500;
}
name 小王
*/
/*
以name屬性為例:
[self setValue:@"小王" forKey:@"name"];
1.首先去模型中查找有么有setName方法匾委,如果有就直接調(diào)用
[self setName:@"小王"];
2.沒有setName方法赂乐,繼續(xù)去模型中查找有沒有name屬性,如果有辐啄,就直接訪問成員屬性
name = @"小王";
3.如果沒有name屬性运嗜,繼續(xù)去模型中查找有沒有_name屬性担租,如果有,就直接訪問成員屬性
_name = @"小王";
4.如果都找不到岭参,就直接報錯尝艘。
*/
}];
}
使用過程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年齡: %d",person.age);
NSLog(@"金錢: %.2f",person.money);
NSLog(@"人所擁有的狗: %@",person.dog);
NSLog(@"狗的類型: %@",person.dog.class);
打印結(jié)果:
姓名: 小王
年齡: 33
金錢: 1000.00
人所擁有的狗: {
name = "wang cai";
price = 500;
}
狗的類型: __NSDictionaryI
- 解決模型中的屬性帶有模型不能正確轉(zhuǎn)換問題
修改Person.m背亥,添加dog的setter方法
#import "Person.h"
#import "Dog.h"
@interface Person ()
// Person 私有屬性
@property (nonatomic, strong) NSString *telephone;
@end
@implementation Person
/**
* 初始化方法
*
* @return <#return value description#>
*/
- (instancetype)init{
self = [super init];
if (self != nil) {
_telephone = @"18200002222";
};
return self;
}
- (void)printTelephone{
NSLog(@"電話為: %@",_telephone);
}
- (void)dictionaryToModel:(NSDictionary *)dic{
[self setValuesForKeysWithDictionary:dic];
}
/**
* 修改dog的set方法,如果傳入的是Dog類型就給dog屬性執(zhí)行屬性賦值操作
* 如果傳入的是dit字典類型就通setValuesForKeysWithDictionary:給Dog模型賦值
*
* @param dog <#dog description#>
*/
- (void)setDog:(id)dog{
if ([dog isKindOfClass:[Dog class]]) {
_dog = dog;
} else {
if (_dog != nil && [dog isKindOfClass:[NSDictionary class]]) {
[_dog setValuesForKeysWithDictionary:dog];
}
}
}
@end
使用過程:
NSDictionary *dic = @{@"name":@"小王",
@"age":@"33",
@"money":@"1000",
@"dog":@{@"name":@"wang cai",
@"price":@"500"},
};
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person dictionaryToModel:dic];
NSLog(@"姓名: %@",person.name);
NSLog(@"年齡: %d",person.age);
NSLog(@"金錢: %.2f",person.money);
NSLog(@"人所擁有的狗: %@",person.dog);
NSLog(@"狗的類型: %@",person.dog.class);
NSLog(@"狗的名字: %@",person.dog.name);
NSLog(@"狗的價格: %d",person.dog.price);
打印結(jié)果:
姓名: 小王
年齡: 33
金錢: 1000.00
人所擁有的狗: <Dog: 0x7fddbad16700>
狗的類型: Dog
狗的名字: wang cai
狗的價格: 500
使用KVC實現(xiàn)模型轉(zhuǎn)字典
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
[person setValue:@"xiao ming" forKeyPath:@"name"];
[person setValue:@"22" forKey:@"age"];
[person setValue:@"1000" forKeyPath:@"money"];
[person setValue:@"旺財" forKeyPath:@"dog.name"];
NSDictionary *dic = [person dictionaryWithValuesForKeys:@[@"name",@"age",@"money",@"dog"]];
NSLog(@"dic = %@",dic);
打印結(jié)果:
dic = {
age = 22;
dog = "<Dog: 0x7feaa9703360>";
money = 1000;
name = "xiao ming";
}
使用KVC取出數(shù)組中所有模型的某個屬性(數(shù)組中所有模型類型相同)
NSMutableArray *personArray = [[NSMutableArray alloc] initWithCapacity:10];
for (int i = 0; i < 10; i++) {
Person *person = [[Person alloc] init];
person.dog = [[Dog alloc] init];
NSString *name = [NSString stringWithFormat:@"xiao ming %d",i];
[person setValue:name forKeyPath:@"name"];
NSString *age = [NSString stringWithFormat:@"2%d",i];
[person setValue:age forKey:@"age"];
NSString *money = [NSString stringWithFormat:@"1%d00",i];
[person setValue:money forKeyPath:@"money"];
[personArray addObject:person];
}
NSArray *nameArray = [personArray valueForKeyPath:@"name"];
NSArray *ageArray = [personArray valueForKeyPath:@"age"];
NSArray *moneyArray = [personArray valueForKeyPath:@"money"];
NSLog(@"nameArray = %@",nameArray);
NSLog(@"ageArray = %@",ageArray);
NSLog(@"moneyArray = %@",moneyArray);
打印結(jié)果:
nameArray = (
"xiao ming 0",
"xiao ming 1",
"xiao ming 2",
"xiao ming 3",
"xiao ming 4",
"xiao ming 5",
"xiao ming 6",
"xiao ming 7",
"xiao ming 8",
"xiao ming 9"
)
ageArray = (
20,
21,
22,
23,
24,
25,
26,
27,
28,
29
)
moneyArray = (
1000,
1100,
1200,
1300,
1400,
1500,
1600,
1700,
1800,
1900
)
在KVC面前所以屬性都是透明可操作的寄锐。