Runtime之模型的属性赋值

拥有一个自己的模型基类。

在进行数据获取的时候会将数据使用模型进行存储,我常规的方法都是给一两个遍历初始化方法,在在里面对字典进行取值赋给相应的模型属性。

这样的好处也有,就是代码直观,快速。由于模型的属性一般是根据网络或者数据库获得的json进行设定的,所以可以考虑使用KVC,而且如果一个模型有很多个属性的话,这样写也比较麻烦。

而使用runtime可以不用考虑属性的个数(有点儿像KVC,但是这只是对runtime的一次练习使用,所以就用了runtime),这样的做法是可以得到一个自己的模型基类,重用性大大提升.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma mark - convenience
/**
* 还是和以前一样的提供两个便利初始化方法,一个对象方法,一个类方法。
*/
- (instancetype) initWithDict:(NSDictionary *)dict{

self = [super init];
if (self) {
[self assginToPropertyWithDictionary:dict];
[self assginToRuntimePropertyWithDictionary:dict];
}
return self;
}
+ (instancetype) itemWithDict:(NSDictionary *)dict{

return [[self alloc] initWithDict:dict];
}

下面有两种方法进行模型属性的赋值,第一种是使用runtime得到模型的所有属性,然后还用KVC进行赋值,第二种是在cocoaChina上看到的一个方法,是得到对应属性的setter方法,然后对属性进行赋值,两种都来感受一下,后面会说两种方法的不同。

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
- (void) assginToRuntimePropertyWithDictionary:(NSDictionary *)dict{

if (dict == nil) {
return;
}

NSArray * allKeys = dict.allKeys;

unsigned int outCount;
objc_property_t * propertys = class_copyPropertyList(self.class, &outCount);

for (int index = 0; index < outCount; index ++) {
objc_property_t property = propertys[index];

NSString * propertyKey = [NSString stringWithUTF8String:property_getName(property)];

if ([allKeys containsObject:propertyKey]) {
// kvc
id propertyValue = [dict objectForKey:propertyKey];
[self setValue:propertyValue forKey:propertyKey];
}
}
free(propertys);
}

cocoaChina

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
- (void) assginToPropertyWithDictionary:(NSDictionary *)dict{

if (dict == nil) {
return;
}

NSArray * allKeys = dict.allKeys;

for (int index = 0; index < allKeys.count; index ++) {

SEL setSEL = [self creatSetterWithPropertyName:allKeys[index]];

if ([self respondsToSelector:setSEL]) {

NSString * value = [dict objectForKey:allKeys[index]];

[self performSelectorOnMainThread:setSEL
withObject:value
waitUntilDone:[NSThread isMainThread]];
}
}
}

/**
* 根据属性名返回setter方法
*/
- (SEL) creatSetterWithPropertyName:(NSString *)propertyName{

propertyName = propertyName.capitalizedString;

NSString * setterName = [NSString stringWithFormat:@"set%@:",propertyName];

return NSSelectorFromString(setterName);
}

在模型的属性为一般的常规属性(id)的时候,比如,NSString,NSArray,NSDictionary等的时候两个方法都可以正确的完成属性赋值。

但是在基本数据类型的属性(非id)的时候,比如,NSInterger,BOOL,int等的时候,由于第二个就必须要进行解包和封包,因此导致不能正确的进行属性赋值,而第一个不论是否进行封包都能够正确进行属性赋值。

因此还是推荐使用runtime进行属性赋值。

一个不可控制的情况是有的时候还是会遇到字典的key和模型的属性不同,这时候的解决办法是需要在类中建立key和属性的映射关系。

在模型基类中写如下方法

1
2
3
4
5
6
7
8
/**
* 为解决属性和字典的key不一样建立的映射
*
* @return 映射字典
*/
-(NSDictionary *) propertyMapDic {
return nil;
}

该方法在有需要的子类中进行重写,设置映射规则

1
2
3
4
5
-(NSDictionary *) propertyMapDic{

return @{@"firstName":@"key1",
@"address":@"key3"};
}

然后在模型基类中实现如下方法,将有映射的字典对原字典进行重新设值

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
/**
* 如果子类中有属性和key不对应,就进行传进来的字典和修改
*
* @param allKeys 原字典的allKeys
* @param newDict 原字典,也是要通过映射之后修改的字典
*/
- (void) dictionaryMapPropertyWithAllKeys:(NSMutableArray *)allKeys toNewDictionary:(NSMutableDictionary *)newDict{

NSDictionary * mapDictionary = [self propertyMapDic];

if (mapDictionary) {
// mapProperty = @"firstName"
// mapKey = @"key1"
[mapDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * mapProperty, NSString * mapKey, BOOL *stop) {
if ([allKeys containsObject:mapKey]) {

[newDict setValue:[newDict objectForKey:mapKey] forKey:mapProperty];
[newDict removeObjectForKey:mapKey];

NSUInteger mapIndex = [allKeys indexOfObject:mapKey];
[allKeys replaceObjectAtIndex:mapIndex withObject:mapProperty];
}
}];
}
}

此时的runtimeAssginToPropertyWithDictionary:方法如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void) runtimeAssginToPropertyWithDictionary:(NSDictionary *)dict{

// ...

NSMutableDictionary * newDict = [NSMutableDictionary dictionaryWithDictionary:dict];

for (int index = 0; index < outCount; index ++) {

// ...

// 如果有属性和key不一样并且子类进行了映射,就进行字典的修改
[self dictionaryMapPropertyWithAllKeys:allKeys toNewDictionary:newDict];

if ([allKeys containsObject:propertyKey]) {
// ...
}
}
free(propertys);
}

这里是自己练习的一个App,作用是仿照快起做的一个快速启动手机上的软件。里面有简单的对控制器的数据源封装。