在MVC设计模式中Model是必不可少的,虽然Controller是核心但Model很好的解决了数据方面的问题。但本篇幅并不是讲解Model的功能和特性,而是从Model的底层实现出发来结合Runtime来探讨下Model是怎么生成的。
Model主要管理的时数据信息结合实际项目数据一般都是通过API请求获取的,然而通过API直接获取到的数据一般都是以JSON形式存在,因此第一步该做的就是对JSON的处理。例:
{
"code": 200,
"msg": "success",
"newslist": [
{
"title": "******",
"hotnum": 6485079,
"digest": "********"
},
{
"title": "*******",
"hotnum": 5412493,
"digest": "*********"
},
{
"title": "*********",
"hotnum": 3420094,
"digest": "***********"
},
]
}
根据上文的JSON信息,生成Model文件NewsListModel:
@interface NewsListModel : NSObject
@property (nonatomic, assign) int code;
@property (nonatomic, copy) NSString *msg;
@property (nonatomic, copy) NSArray *newslist;
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [super init]) {
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *attributes = [NSMutableArray array];
// 反射机制
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyNmae = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyNmae];
NSString *propertyArttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyArttribute];
}
free(properties);
//
for (NSString *key in keys) {
id value = [dict valueForKey:key];
if (value == nil) {
continue;
}
[self setValue:value forKey:key];
}
}
return self;
}
- (instancetype)initWithDict:(NSDictionary *)dict;方法需要传入的是一个NSDictionary类型的参数,因此需要把JSON转成NSDictionary这个后面碰到再讲解。Model的方便在于通过自带的属性就能获取到对应JSON中相同属性名的值,要实现这一点就需要把JSON对象中的属性和Model中的属性一一对应起来,如此当使用Model值时就能获取到对应起来的值。
Runtime通过属性反射机制,用动态的方式取出对象中的属性并进行自动绑定值。首先通过
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
函数来获取类的属性变量,通过List可以显而易见的看出生成的properties是以数组的形式出现的,因此通过for循环就可以拿到每个属性。其中通过property_getName函数可以获取到每个属性的名称,通过property_getAttributes函数获取每个属性对应的类型。 free(properties)的作用是用来释放properties指向的内存。
然后通过for循环获取来的属性名称列表,把每个属性名称作为key值,匹配传入的NSDictionary的key值,并把拿到的值映射到当前的属性上。这也是一开始为什么要把JSON转成NSDictionary以及Model类的属性名称要与传入的NSDictionary的key值名称一致的原因。
JSONModel的核心就是利用Runtime的class_copyPropertyList得到类的属性列表然后遍历,再利用property_getAttributes得到属性的类型,最后在KVC接收得到的值。如此就能快速的把JSON数据转化为Objective-C的数据类型,使用时只需继承并加入需要的对应属性即可。
Runtime还提供了另一种方式来处理字典到Model的转换过程。上面处理的数据的第一层那接下来就通过例子来处理第二层newslist内的数据:
@interface ListInfoModel : NSObject
@property (nonatomic, copy) NSString *title;
@property (nonatomic, assign) int hotnum;
@property (nonatomic, copy) NSString *digest;
+ (instancetype)modelWithDictionary:(NSDictionary *)dict;
@end
@implementation ListInfoModel
+ (instancetype)modelWithDictionary:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
id objc = [[self alloc] init];
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *key = [ivarName substringFromIndex:1];
id value = dict[key];
if (!value) {
NSDictionary *customKeyDict = [self modelCustomPropertyMapper];
NSString *customKey = customKeyDict[key];
value = dict[customKey];
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
+ (NSDictionary *)modelCustomPropertyMapper {
return @{};
}
@end
通过前几篇幅中的讲解对Ivar应该有一定的理解了,Runtime正式首先通过Ivar以及class_copyIvarList函数来获取类中的成员变量列表;然后循环成员变量列表并把每个成员变量作为Key值;在通过key获取到对应的值,并映射到对应的属性上。当然还需要做一些其它的处理来保证循环的正确性。
其实通过对比不难发现以上的两种方式是大同小异,目的都是通过循环遍历的方式筛查出对应的key,然后通过key获取到value,并把值映射到与之对应的Model属性上。正是Runtime的强大功能造就了一些经典的第三方(库),也给平时的工作带来的巨大的便利。
然而在实际的项目中都会碰到不同的数据类型,对于非集合对象的判断相对是比较简单和单一的,但对于集合类NSDictionary和NSArray来说就必须进行二级转换。因此对value进行类型的判断从而根据类型进行进一步的转换是很有必要的。当遇到NSDictionary类型时则需要进一步的对其进行字典转模型处理
Class modalClass = NSClassFromString(ivartype);
if (modalClass) {
value = [modalClass modelWithDictionary:value];
}
同理当碰到NSArray类型时则需要对其进行遍历,如果其中包含的是NSDictionary的话则重复以上操作即可:
Class classModel = NSClassFromString(type);
NSMutableArray *arrayM = [NSMutableArray array];
for (NSDictionary *dic in value) {
id model = [classModel modelWithDictionary:dic];
[arrayM addObject:model];
}
value = arrayM;
区别则在于需要重新组件数组模型并赋值给返回对象。
虽然实际项目中的数据结构复杂于此但基本难点不外乎以上几点。然而真正需要做的肯定远不如此,如对空值、基本数据类型等判断当然是越精确对Model的转换肯定是越有利的。