iOS設計模式 —— KVC

 

刨根問底KVC

KVC 全稱 key valued coding 鍵值編碼html

反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性.JAVA,C#都有這個機制。ObjC也有,因此你根部沒必要進行任何操做就能夠進行屬性的動態讀寫,就是KVC。ios

KVC的操做方法由NSKeyValueCoding提供,而他是NSObject的類別,也就是說ObjC中幾乎全部的對象都支持KVC操做。服務器

Person.happ

1
2
3
4
5
6
7
8
9
10
11
12
@interface Person : NSObject
{
int weight;
}
@property(nonatomic,readonly,copy) NSString *name;
@property(nonatomic,readonly, assign) int age;
@property(nonatomic,strong) Dog * dog;
@property(nonatomic,assign) id ID;

-(instancetype)initWithDict:(NSDictionary *)dict;

@end

Person.mui

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35


@implementation Person
{
int _height;
}

-(instancetype)initWithDict:(NSDictionary *)dict
{
if (self=[super init])
{
//字典轉模型的經常使用語句
[self setValuesForKeysWithDictionary:dict];
}
return self;
}

//當key的值是沒有定義時,設置會執行的方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {

if ([key isEqualToString:@"id"])
{
self.ID = value;
}
}


//當key的值是沒有定義時,取值時執行的方法
- (id)valueForUndefinedKey:(NSString *)key {

if ([key isEqualToString:@"id"]) {
return self.ID;
}
return [NSNull null];
}

main.m編碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Person * p1 = [[Person alloc]init];
Dog * d1 = [[Dog alloc] init];
p1.dog = d1;

//setValue:屬性值 forKey:屬性名(用於簡單路徑)
//使用KVC間接修改對象屬性時,系統會自動判斷對象屬性的類型,並完成轉換。如該程序中的「170」.
[p1 setValue:@"170" forKey:@"height"];
[p1 setValue:@"70" forKey:@"weight"];
[p1 setValue:@"1" forKey:@"id"];

//setValue:屬性值 forKeyPath:屬性路徑(用於複合路徑)
//用KVC取一個嵌套層次很深的路徑的時候,只要給它一個路徑就能把想要的屬性給拿出來。(.能夠理解爲路徑。一直一直進入)。可以幫助咱們很方便的編碼。
[p1 setValue:@"dahuan" forKeyPath:@"dog.name"];

//valueForKey:屬性名 、valueForKeyPath:屬性名(用於複合路徑)
NSLog(@"height = %d weight = %d id = %@ dog.name = %@" ,[[p1 valueForKey:@"height"] intValue],[[p1 valueForKey:@"weight"] intValue],[p1 valueForKey:@"id"],[p1 valueForKeyPath:@"dog.name"]);

log日誌:height = 170 weight = 70 id = 11111 dog.name = dahuan
全部的屬性均可以賦值成功


NSDictionary * dict = @{@"height":@"160",
@"weight":@"60",
@"id":@"11101"
};

Person * p2 = [[Person alloc] initWithDict:dict];

NSLog(@"height = %d weight = %d id = %@",[[p2 valueForKey:@"height"] intValue],[[p2 valueForKey:@"weight"] intValue],[p2 valueForKey:@"id"]);

log日誌:height = 160 weight = 60 id = 11101
全部的屬性均可以賦值成功

KVC查找屬性的順序

  1. 用@property定義的屬性的key值
  2. setter方法的key值
  3. 直接訪問成員變量,先找key,若是找不到,再找_key
  4. 以上三種都未找到就會調用- (void)setValue:(id)value forUndefinedKey:(NSString *)key 方法。
  5. 若是沒有重寫setValue:forUndefinedKey程序會立刻崩潰。

注意:KVC能夠訪問成員變量,不管是否提供getter/setter方法,不管可見性是怎樣,是否有readonly修飾。atom

setValue:forUndefinedKey與valueForUndefinedKey的應用

KVC的主要用途無非是ORM映射,就是將dictionary轉換成model,但有些服務器返回的字段有多是oc的關鍵字好比‘id’,’description’等。如上代碼舉得id的例子,咱們沒法讓@property後面key值爲id,因而使用大寫的ID代替,KVC是區分大小寫的咱們不用擔憂。這時咱們只需在setValue:forUndefinedKey把id的key值賦值給ID的key值,就能夠避免關鍵字的尷尬。spa

KVC的逆向使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Person * p1 = [[Person alloc]init];
[p1 setValue:@"170" forKey:@"height"];
[p1 setValue:@"70" forKey:@"weight"];
[p1 setValue:@"11111" forKey:@"id"];


NSArray * arr = @[@"height",@"weight",@"id"];
NSDictionary * dict = [p1 dictionaryWithValuesForKeys:arr];
NSLog(@"%@",dict);

log日誌:
{
height = 170;
id = 11111;
weight = 70;
}

NSArray/NSSet等都支持KVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Person * p1 = [[Person alloc]init];
Dog * d1 = [[Dog alloc] init];
d1.name = @"iPhone";
p1.dog = d1;

Person * p2 = [[Person alloc]init];
Dog * d2 = [[Dog alloc] init];
d2.name = @"ios";
p2.dog = d2;

NSArray *persons=@[p1,p2];

NSArray *arrayM=[persons valueForKeyPath:@"dog.name"];
NSLog(@"%@",arrayM);

log日誌:
(
iPhone,
ios
)

KVC計算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Person * person = [[Person alloc] init];

NSMutableArray * books = [NSMutableArray array];
for (int i = 0; i < 10; i++) {
Book * book = [[Book alloc] init];
book.price = i;
[books addObject:book];
}

person.books = books;

NSNumber *bookCount = [person valueForKeyPath:@"books.@count"];
NSLog(@"book count :%@", bookCount);

NSNumber *sum = [person valueForKeyPath:@"books.@sum.price"];
NSLog(@"sum :%@", sum);

NSNumber *avg = [person valueForKeyPath:@"books.@avg.price"];
NSLog(@"vag :%@", avg);

NSNumber *min = [person valueForKeyPath:@"books.@min.price"];
NSLog(@"min :%@", min);

NSNumber *max = [person valueForKeyPath:@"books.@max.price"];
NSLog(@"max :%@", max);

KVC底層實現

1
2
3
4
5
6
7
8
9
好比說以下的一行KVC的代碼:

[person setValue:@"dahuan" forKey:@"name"];

就會被編譯器處理成:

SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (person->isa,sel);
method(person, sel, @"dahuan", @"name");

KVC與runtime應用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#import "MyModel.h"
#import <objc/runtime.h>

@implementation MyModel

//解檔
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
unsigned int count = 0;
//獲取類中全部成員變量名
Ivar *ivar = class_copyIvarList([MyModel class], &count);
for (int i = 0; i<count; i++) {
Ivar iva = ivar[i];
const char *name = ivar_getName(iva);
NSString *strName = [NSString stringWithUTF8String:name];
//進行解檔取值
id value = [decoder decodeObjectForKey:strName];
//利用KVC對屬性賦值
[self setValue:value forKey:strName];
}
free(ivar);
}
return self;
}

//歸檔
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count;
Ivar *ivar = class_copyIvarList([MyModel class], &count);
for (int i=0; i<count; i++) {
Ivar iv = ivar[i];
const char *name = ivar_getName(iv);
NSString *strName = [NSString stringWithUTF8String:name];
//利用KVC取值
id value = [self valueForKey:strName];
[encoder encodeObject:value forKey:strName];
}
free(ivar);
}
@end

最後附蘋果KVC官方文檔:日誌

https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/KeyValueCoding.htmlcode

另外.....

個人願望是.......

世界和平.........

相關文章
相關標籤/搜索