运行时(Runtime)篇幅六

96 阅读5分钟

在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的强大功能造就了一些经典的第三方(库),也给平时的工作带来的巨大的便利。

然而在实际的项目中都会碰到不同的数据类型,对于非集合对象的判断相对是比较简单和单一的,但对于集合类NSDictionaryNSArray来说就必须进行二级转换。因此对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的转换肯定是越有利的。