1、使用目的:實現JSON 與model之間的轉換。 我們經常要從伺服器上獲取JSON數據,將其轉化為Model。 這個過程是無疑是痛苦的。對於JSON數據量相對較少,或者Model裡面的屬性值較少的情況,處理起來不大費勁。但上架的應用大多是數據量巨大,與後臺交互頻繁的。更糟的是,後臺介面頻繁變化, ...
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"}]; }
本文的代碼可下載下麵文件: