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
#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
#import <UIKit/UIKit.h> //@class OneModel; @class User; @interface TableViewCell : UITableViewCell //@property (strong,nonatomic) OneModel *oneModel; @property (strong,nonatomic) User *oneModel; @end
TableViewCell.m
#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
#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 <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
对比之前JSONModel的例子,我们用MJExtention来实现:
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));
下面我画了个消息机制的原理图
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
交换方法的原理图片如下
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的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"}]; }
本文的代码可下载下面文件:
示例代码