MJExtension和JSONModel的使用

1、使用目的:实现JSON 与model之间的转换。

我们经常要从服务器上获取JSON数据,将其转化为Model。

这个过程是无疑是痛苦的。对于JSON数据量相对较少,或者Model里面的属性值较少的情况,处理起来不大费劲。但上架的应用大多是数据量巨大,与后台交互频繁的。更糟的是,后台接口频繁变化,那么维护起来就相当费劲了,因为你每次都要根据新的接口文档来逐一解释数据。往往每次要花你半天时间去修改、调试代码。

2、JSONModel

JSON -> Dictionary -> Model

以下面的JSON文件为例:

{
    "data" : [
                    {
                        "name" : "张三",
                        "gender" : "male"
                    },
                    {
                        "name" : "李四",
                        "gender" : "female"
                    },
                    {
                        "name" : "黄五",
                        "gender" : "male"
                    }
                ]
}

上述JSON分析:

Data 就是 数组。数组里每一个元素也是一个字典。

假设我们tableView里每个cell都要展示一个名字和性别,那么我们需要把一组名字和性别做成一个model来使用。

所以在逻辑上,这个json就是两类mode:

1、

{
    "name" : "张三",
    "gender" : "male"
  }

2、

{

    "data" : [
                    {
                        "name" : "张三",
                        "gender" : "male"
                    },
                    {
                        "name" : "李四",
                        "gender" : "female"
                    },
                    {
                        "name" : "黄五",
                        "gender" : "male"
                    }
                ]
}

一般来说,一个字典就是一个model

所以,我们要创建的就是这两个model类。

Model.h文件------------------------------------

#import <JSONModel/JSONModel.h>

@protocol OneModel //<NSObject>

@end

@interface OneModel : JSONModel

@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *gender;


@end

@interface Model : JSONModel

@property (strong, nonatomic) NSArray<OneModel> *data;

@end

Model.m文件------------------------------------

#import "Model.h"

@implementation OneModel

@end

@implementation Model

@end

注意两点:

1、需要做成多少个model,就该有多少个@interface和@implementation

2、Model里面的属性名必须与json里面的一样。

TableViewController.h

#import <UIKit/UIKit.h>
//@class Model;
@class MyMJModel;
@interface TableViewController : UITableViewController

//@property(nonatomic,strong) Model *model;
@property(nonatomic,strong) MyMJModel *model;

@end

TableViewController.m

MJExtension和JSONModel的使用

#import "TableViewController.h"
#import "Model.h"
#import "MyMJModel.h"
#import "TableViewCell.h"
#import "Status.h"
#import <MJExtension/MJExtension.h>
@interface TableViewController ()

@end

@implementation TableViewController
static NSString *reuseID = @"reuse";
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.tableView registerClass:[TableViewCell class] forCellReuseIdentifier:reuseID];
    
    
    [self initData];
}

#pragma mark - 获取JSON数据
- (void)initData {
    NSString *path = [[NSBundle mainBundle]pathForResource:@"Model" ofType:@"json"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    if (data) {
        NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

//        self.model = [[Model alloc]initWithDictionary:dict error:nil];

        [MyMJModel mj_setupObjectClassInArray:^NSDictionary *{
            return @{
                     @"users" : @"User"
                     };
        }];
        
        self.model = [MyMJModel mj_objectWithKeyValues:dict];
        
    }
    
    [self objectArray2keyValuesArray];
    
}

#pragma mark - 隐藏状态栏
- (BOOL)prefersStatusBarHidden{
    return YES;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    
    return self.model.users.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseID forIndexPath:indexPath];
    
    cell.oneModel = self.model.users[indexPath.row];
    return cell;
}

/**
 *  简单的字典 -> 模型
 */
- (void) keyValues2object
{
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"name" : @"Jack",
                           @"gender" : @"male",
                           };
    
    // 2.将字典转为User模型
    User *user = [User mj_objectWithKeyValues:dict];
    
    // 3.打印User模型的属性
    NSLog(@"name = %@, gender = %@", user.name, user.gender);
}

/**
 *  复杂的字典 -> 模型 (模型里面包含了模型)
 */
- (void) keyValues2object2
{
    // 1.定义一个字典
    NSDictionary *dict = @{
                           @"text" : @"是啊,今天天气确实不错!",
                           
                           @"user" : @{
                                   @"name" : @"Jack",
                                   @"gender" : @"male"
                                   }
                           };
    
    // 2.将字典转为Status模型
    Status *status = [Status mj_objectWithKeyValues:dict];
    
    // 3.打印status的属性
    NSString *text = status.text;
    NSString *name = status.user.name;
    NSString *gender = status.user.gender;
    NSLog(@"text=%@, name=%@, gender=%@", text, name, gender);
}

/**
 *  字典数组 -> 模型数组
 */
- (void) keyValuesArray2objectArray
{
    // 1.定义一个字典数组
    NSArray *dictArray = @[
                           @{
                               @"name" : @"Jack",
                               @"gender" : @"male",
                               },
                           
                           @{
                               @"name" : @"Rose",
                               @"gender" : @"female",
                               }
                           ];
    
    // 2.将字典数组转为User模型数组
    NSArray *userArray = [User mj_objectArrayWithKeyValuesArray:dictArray];
    
    // 3.打印userArray数组中的User模型属性
    for (User *user in userArray) {
        NSLog(@"name=%@, gender=%@", user.name, user.gender);
    }
}

/**
 *  模型 -> 字典
 */
- (void) object2keyValues
{
    // 1.新建模型
    User *user = [[User alloc] init];
    user.name = @"Jack";
    user.gender = @"lufy.png";
    
    Status *status = [[Status alloc] init];
    status.user = user;
    status.text = @"今天的心情不错!";
    
    // 2.将模型转为字典
    //    NSDictionary *dict = [status keyValues];
    NSDictionary *dict = status.mj_keyValues;
    NSLog(@"%@", dict);
}

/**
 *  模型数组 -> 字典数组
 */
-(void) objectArray2keyValuesArray
{
    // 1.新建模型数组
    User *user1 = [[User alloc] init];
    user1.name = @"Jack";
    user1.gender = @"male";
    
    User *user2 = [[User alloc] init];
    user2.name = @"Rose";
    user2.gender = @"female";
    
    NSArray *userArray = @[user1, user2];
    
    // 2.将模型数组转为字典数组
    NSArray *dictArray = [User mj_keyValuesArrayWithObjectArray:userArray];
    NSLog(@"%@", dictArray);
}

@end

TableViewCell.h

MJExtension和JSONModel的使用

#import <UIKit/UIKit.h>
//@class OneModel;
@class User;
@interface TableViewCell : UITableViewCell

//@property (strong,nonatomic) OneModel *oneModel;
@property (strong,nonatomic) User *oneModel;

@end

TableViewCell.m

MJExtension和JSONModel的使用

#import "TableViewCell.h"
#import "Model.h"
#import "MyMJModel.h"

@implementation TableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
    
    self = [super initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier];
    
    if (self) {
        
    }
    
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    // Initialization code
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];
    
    // Configure the view for the selected state
}

//- (void)setOneModel:(OneModel *)oneModel {
//    _oneModel = oneModel;
//    self.textLabel.text = oneModel.name;
//    self.detailTextLabel.text = oneModel.gender;
//}
- (void)setOneModel:(User *)oneModel {
    _oneModel = oneModel;
    
    self.textLabel.text = oneModel.name;
    self.detailTextLabel.text = oneModel.gender;
}

@end

Model.h

MJExtension和JSONModel的使用

#import <JSONModel/JSONModel.h>

@protocol OneModel //<NSObject>

@end

@interface OneModel : JSONModel

@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *gender;


@end

@interface Model : JSONModel

@property (strong, nonatomic) NSArray<OneModel> *data;

@end

Model.m

MJExtension和JSONModel的使用

#import <JSONModel/JSONModel.h>

@protocol OneModel //<NSObject>

@end

@interface OneModel : JSONModel

@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *gender;


@end

@interface Model : JSONModel

@property (strong, nonatomic) NSArray<OneModel> *data;

@end

步骤总结:

步骤一:通过CocoaPod安装JSONModel。(不再赘述)

步骤二:分析JSON数据和业务需求,搭建UITableViewCell、UITableViewController等代码。(不再赘述)

步骤三:根据分析JSON数据,写Model文件,只需写.h文件,.m文件对应类的个数写@implementation@end,其他不用写。

3、MJExtention:

MJExtension是一套字典和模型之间互相转换的超轻量级框架

JSON --> Model、Core Data Model

JSONString --> Model、Core Data Model

Model、Core Data Model --> JSON

JSON Array --> Model Array、Core Data Model Array

JSONString --> Model Array、Core Data Model Array

Model Array、Core Data Model Array --> JSON Array

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

对比之前JSONModel的例子,我们用MJExtention来实现:

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

 

MJExtension的runtime原理:

一.runtime介绍

runtime翻译就是运行时,我们称为运行时机制.在OC中最重要的体现就是消息发送机制.

1)在C语言中,程序在编译过程中就决定调用哪个函数.

2)在OC中,编译的时候不会决定调用哪个函数,只要声明了这个函数即可.只有在真正运行的时候,才会去决定调用哪个函数.

二.runtime用法,总结了下大概有以下几种用法.

1>发送消息

1)OC调用方法本质就是发送消息,要用消息机制,需要导入<objc/message.h>才可以使用.

2)objc_msgSend,是只有对象才能发送消息,只能以objc开头.

    // 创建person对象

    Person *p = [[Person alloc] init];

    // 调用对象方法    [p read];

    // 本质:让对象发送消息    objc_msgSend(p, @selector(read));

    // 调用类方法的方式:两种

    // 第一种通过类名调用    [Person read];

    // 第二种通过类对象调用

    [[Person class] read];

    // 用类名调用类方法,底层会自动把类名转换成类对象调用

    // 本质:让类对象发送消息

    objc_msgSend([Person class], @selector(read));

下面我画了个消息机制的原理图

MJExtension和JSONModel的使用

2>交换方法

个人觉得有点类似于分类或者是类扩展,但是也有区别,它可以保证在系统原有的方法基础上加一些其他方法

@implementation UIImage (Image)// 加载分类到内存的时候调用

+ (void)load
{
    // 交换方法
    // 获取imageWithName方法地址
    Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));

    // 获取imageWithName方法地址
    Method imageName = class_getClassMethod(self, @selector(imageNamed:));

    // 交换方法地址,相当于交换实现方式    
  method_exchangeImplementations(imageWithName, imageName);
}

// 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
// 既能加载图片又能打印

+ (instancetype)imageWithName:(NSString *)name
{
    // 这里调用imageWithName,相当于调用imageName
    UIImage *image = [self imageWithName:name];

    if (image == nil) {
        NSLog(@"加载空的图片");
    }
    return image;

}

@end

交换方法的原理图片如下

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

3>动态添加方法(performSelector)

如果一个类有很多方法,加载到内存中生成方法列表需要消耗很多内存,使用动态添加方法可以节省内存.

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    Person *p = [[Person alloc] init];


    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
    // 动态添加方法就不会报错    [p performSelector:@selector(eat)];
}
@end

@implementation Person// void(*)()// 默认方法都有两个隐式参数,void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 动态添加eat方法
        class_addMethod(self, @selector(eat), eat, "v@:");

    }
    return [super resolveInstanceMethod:sel];
}

@end

4>动态添加属性

原理就是给一个类声明属性,就是给一个类添加关联,而不是把属性的内存添加到这个类的内存.

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    // 给系统NSObject类动态添加属性name
    NSObject *objc = [[NSObject alloc] init];

    objc.name = @"cjh";

    NSLog(@"%@",objc.name);
}

@end

// 定义关联的keystatic const char *key = "name";

@implementation NSObject (Property)
- (NSString *)name
{
    // 根据关联的key,获取关联的值。
    return objc_getAssociatedObject(self, key);
}

- (void)setName:(NSString *)name
{
      objc_setAssociatedObject(self,key,name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);      
}

@end

5>字典转模型

MJExtension框架我们应该不陌生,里面字典转模型就是利用了runtime来实现的.

1)首先,模型设计上,属性我们通常是根据字典来设计的,但是每次都一个一个来写的话很麻烦,我们可以设计一个分类,根据字典生成一个对应的字符串,就是我们想要的模型设计属性.

@implementation NSObject (Log)

// 自动打印属性字符串
+ (void)resolveDict:(NSDictionary *)dict{

    // 拼接属性字符串代码
    NSMutableString *strM = [NSMutableString string];

    // 1.遍历字典,把字典中的所有key取出来,生成对应的属性代码
    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

         NSString *type;

        if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {

            type = @"NSString";

        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){

            type = @"NSArray";

        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){

            type = @"int";

        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){

            type = @"NSDictionary";

        }

        // 属性字符串
        NSString *str;
        if ([type containsString:@"NS"]) {

            str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];

        }else{

            str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];

        }

        // 每生成属性字符串,就自动换行。
        [strM appendFormat:@"\n%@\n",str];
    }];

    // 把拼接好的字符串打印出来,就好了。
    NSLog(@"%@",strM);

}
@end

2)利用runtime赋值,注意一下的区别.

 KVC: 遍历字典中所有key,去模型中查找

runtime: 遍历模型中所有属性,去字典中查找对应value,然后在赋值

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 思路:遍历模型中所有属性-》使用运行时

    // 0.创建对应的对象
    id objc = [[self alloc] init];

    // 1.利用runtime给对象中的成员属性赋值
    unsigned int count;

    // 获取类中的所有成员属性(使用copy,不影响内部的ivar)
    Ivar *ivarList = class_copyIvarList(self, &count);


    for (int i = 0; i < count; i++) {
        // 根据角标,从数组取出对应的成员属性
        Ivar ivar = ivarList[i];

        // 获取成员属性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 处理成员属性名->字典中的key
        // 从第一个角标开始截取
        NSString *key = [name substringFromIndex:1];

        // 根据成员属性名去字典中查找对应的value
        id value = dict[key];

        // 二级转换:如果字典中还有字典,也需要把对应的字典转换成模型
        // 判断下value是否是字典
        if ([value isKindOfClass:[NSDictionary class]]) {

            // 获取成员属性类型
           NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];

            // 裁剪类型字符串
            NSRange range = [type rangeOfString:@"\""];

           type = [type substringFromIndex:range.location + range.length];

            range = [type rangeOfString:@"\""];

            // 裁剪到哪个角标,不包括当前角标
          type = [type substringToIndex:range.location];

            // 根据字符串类名生成类对象
            Class modelClass = NSClassFromString(type);

            if (modelClass) { // 有对应的模型才需要转
                // 把字典转模型
                value  =  [modelClass modelWithDict:value];
            }
        }

        // 三级转换:NSArray中也是字典,把数组中的字典转换成模型.
        // 判断值是否是数组
        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;

                // 获取数组中字典对应的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
               Class classModel = NSClassFromString(type);

                NSMutableArray *arrM = [NSMutableArray array];

                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                  id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型数组赋值给value
                value = arrM;

            }
        }

        if (value) { // 有值,才需要给模型的属性赋值

            // 利用KVC给模型中的属性赋值            
            [objc setValue:value forKey:key];

        }
    }
    return objc;
}


@end

4、MJExtention与JSONModel的对比:

1、json与model之间的转换效率对比:

MJExtension和JSONModel的使用

MJExtension和JSONModel的使用

运行效率,MJExtension是JSONModel的20倍。

MJExtension > JSONModel > Mantle

2、使用复杂度对比:

对于复杂的字典,JSONModel处理的原理是通过协议名,去找到名字与之一样的类,而装成一个model的。所以JSONModel比MJExtention要多写一个代理,即便这个代理是空的。

但MJExtention要在使用的时候说明数组里每个元素的类是什么类。

所以,在使用复杂度上,各有千秋。

3、耦合度对比:

MJExtention用的是类别category,而JSONModel在写model类时,则必须继承自JSONModel。

所以在耦合度的对比上,MJExtention占优!

5、特殊情况处理:

1、Objective-C里的关键字,例如,id

MJExtension:id是Objective-C里的关键字,我们一般用大写的ID替换,但是往往服务器给我们的数据是小写的id,这个时候就可以用MJExtension框架里的方法转换一下:

+ (NSDictionary *)mj_replacedKeyFromPropertyName  {

    return @{@"ID": @"id"};
}

JSONModel:

在你的model的.m(实现)文件中:

+ (JSONKeyMapper *)keyMapper  {

    return [[JSONKeyMapper alloc] initWithDictionary:@{@"description" : @"bank_description", @"id" : @"bank_id"}];

}

本文的代码可下载下面文件:

示例代码