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"}];
}
本文的代码可下载下面文件: