寫(xiě)在前面的話(huà)
這篇文章的通過(guò)runtime實(shí)現(xiàn)字典轉(zhuǎn)模型是參考(抄襲)iOS 模式詳解—「runtime面試、工作」看我就 ?? 了 _.中runtime 字典轉(zhuǎn)模型,并且在此基礎(chǔ)上做了以下擴(kuò)展:
- 添加:屬性名映射到字典中對(duì)應(yīng)的key的方法,如id -> ID;
- 修復(fù):當(dāng)模型中的數(shù)組中不全是某一個(gè)模型的時(shí)候,會(huì)引起崩潰的問(wèn)題。如數(shù)組中有8個(gè)元素,其中7個(gè)是模型,還有一個(gè)是字符串;
Github 傳送門(mén)
需要考慮以下三種情況
- 當(dāng)字典中的key和模型的屬性匹配不上;
- 模型中嵌套模型(模型的屬性是另外一個(gè)模型對(duì)象);
- 模型中的數(shù)組中裝著模型(數(shù)組中的元素是一個(gè)模型)。
一、使用runtime將字典轉(zhuǎn)成模型
1. 思路
使用runtime遍歷出模型中的所有屬性,根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value給模型屬性賦值
2. 代碼
1) 定義一個(gè)Student的模型,其屬性如下:
Student.h文件
#import <Foundation/Foundation.h>
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSNumber *height;
@property (nonatomic, strong) NSNumber *ID;
@end
Student.m文件
@implementation Student
@end
2)對(duì)NSObject擴(kuò)展一個(gè)分類(lèi)NSObject+DictionaryToModel
NSObject+DictionaryToModel.h文件
+ (instancetype)modelWithDict:(NSDictionary *)dict;
NSObject+DictionaryToModel.m文件,一定要導(dǎo)入<objc/message.h>
#import "NSObject+DictionaryToModel.h"
#import <objc/message.h>
@implementation NSObject (DictionaryToModel)
/*
* 根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value并賦值給模型的屬性
* 遍歷取出所有屬性
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
//1. 創(chuàng)建對(duì)應(yīng)的對(duì)象
id objc = [[self alloc] init];
//2. 利用runtime給對(duì)象中的屬性賦值
/*
Ivar: 成員變量;
class_copyIvarList(): 獲取類(lèi)中的所有成員變量;
第一個(gè)參數(shù):表示獲取哪個(gè)類(lèi)的成員變量;
第二個(gè)參數(shù):表示這個(gè)類(lèi)有多少成員變量;
返回值Ivar *:指的是一個(gè)ivar數(shù)組,會(huì)把所有成員變量放在一個(gè)數(shù)組中,通過(guò)返回?cái)?shù)組就全部獲取到。
count: 成員變量個(gè)數(shù)
*/
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)];
// 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的value
id value = dict[key];
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
3)調(diào)用+ (instancetype)modelWithDict:(NSDictionary *)dict
#import "Student.h"
#import "NSObject+DictionaryToModel.h"
......
NSDictionary *studentInfo = @{@"name" : @"Kobe Bryant",
@"age" : @(18),
@"height" : @(190),
@"id" : @(20160101),
@"gender" : @(1)};
Student *student = [Student modelWithDict2:studentInfo];
NSLog(@"student = %@", student);
4)模型的轉(zhuǎn)換結(jié)果

從上圖可以看出
-
student.ID沒(méi)有賦值成功,是因?yàn)樵跀?shù)據(jù)中沒(méi)有ID這個(gè)key(Objective-C 中id是保留字,所以student的屬性這里只能用ID) - 對(duì)于這種模型屬性名和數(shù)據(jù)中key不對(duì)應(yīng)的問(wèn)題,接下來(lái)會(huì)講如何解決。
<h3 id="2"></h3>
二、當(dāng)字典中的key和模型的屬性匹配不上
1. 思路
如果字典中的key和模型的屬性匹配不上,可以做一個(gè)映射。將屬性名映射到字典中對(duì)應(yīng)的key上
2. 代碼
這里代碼接著上面的代碼使用
1)在NSObject的分類(lèi)NSObject+DictionaryToModel中添加映射方法
NSObject+DictionaryToModel.h文件中
+ (NSDictionary *)modelCustomPropertyMapper;
NSObject+DictionaryToModel.m文件中
#import "NSObject+DictionaryToModel.h"
#import <objc/message.h>
@implementation NSObject (DictionaryToModel)
/*
* 根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value并賦值給模型的屬性
* 遍歷取出所有屬性
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
//1. 創(chuàng)建對(duì)應(yīng)的對(duì)象
id objc = [[self alloc] init];
//2. 利用runtime給對(duì)象中的屬性賦值
/*
Ivar: 成員變量;
class_copyIvarList(): 獲取類(lèi)中的所有成員變量;
第一個(gè)參數(shù):表示獲取哪個(gè)類(lèi)的成員變量;
第二個(gè)參數(shù):表示這個(gè)類(lèi)有多少成員變量;
返回值Ivar *:指的是一個(gè)ivar數(shù)組,會(huì)把所有成員變量放在一個(gè)數(shù)組中,通過(guò)返回?cái)?shù)組就全部獲取到。
count: 成員變量個(gè)數(shù)
*/
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)];
// 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的value
id value = dict[key];
//如果通過(guò)屬性名取不到對(duì)應(yīng)的value,則更換屬性名對(duì)應(yīng)的映射名來(lái)取值
if (!value) {
NSDictionary *customKeyDict = [self modelCustomPropertyMapper];
NSString *customKey = customKeyDict[key];
value = dict[customKey];
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
//這里放一個(gè)空的字典,真正實(shí)現(xiàn)這個(gè)映射方法的地方是在模型中,模型中會(huì)將此方法重寫(xiě)
+ (NSDictionary *)modelCustomPropertyMapper {
return @{};
}
@end
2)在模型中實(shí)現(xiàn)映射方法
//重寫(xiě)NSObject+DictionaryToModel分類(lèi)中的映射方法
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"ID" : @"id"};
}
3)模型轉(zhuǎn)換的結(jié)果如下

<h3 id="3"></h3>
三、模型中嵌套模型
1. 思路
模型中嵌套模型就是字典中嵌套字典,當(dāng)給模型的模型賦值的時(shí)候,再調(diào)用一次字典轉(zhuǎn)模型就可以了。其實(shí)就是遞歸調(diào)用
2. 代碼
1) 定義一個(gè)ZClass模型,其屬性具體如下:
ZClass.h文件中,包含了Student模型。
#import <Foundation/Foundation.h>
#import "Student.h"
@interface ZClass : NSObject
@property (nonatomic, strong) Student *student;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, copy) NSString *header;
@end
ZClass.m文件中
#import "ZClass.h"
#import "NSObject+DictionaryToModel.h"
@implementation ZClass
@end
2) 在NSObject的分類(lèi)NSObject+DictionaryToModel中
完善一下+ (instancetype)modelWithDict:(NSDictionary *)dict方法,這里我寫(xiě)在+ (instancetype)modelWithDict2:(NSDictionary *)dict中。
NSObject+DictionaryToModel.h文件
+ (instancetype)modelWithDict2:(NSDictionary *)dict;
NSObject+DictionaryToModel.m文件,一定要導(dǎo)入<objc/message.h>
+ (instancetype)modelWithDict2:(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)];
// 獲取成員變量類(lèi)型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 替換: @\"Student\" -> Student
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
NSString *key = [ivarName substringFromIndex:1];
id value = dict[key];
if (!value) {
NSDictionary *customKeyDict = [self modelCustomPropertyMapper];
NSString *customKey = customKeyDict[key];
value = dict[customKey];
}
//如果value是一個(gè)字典,并且其類(lèi)型是自定義對(duì)象才需要轉(zhuǎn)換。不是OC中的數(shù)據(jù)類(lèi)型,如:NSString, NSArray, NSDictionary, NSMutableArray, NSMutableDictionary, NSNumber等
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(ivarType);
if (modelClass) {
//如果modelClass存在,則進(jìn)入遞歸調(diào)用
value = [modelClass modelWithDict2:value];
}
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
3)調(diào)用+ (instancetype)modelWithDict2:(NSDictionary *)dict
NSDictionary *classInfo = @{@"student" : studentInfo,
@"title" : @"Math",
@"subtitle" : @"Global",
@"header" : @"Shanghai"};
ZClass *class1 = [ZClass modelWithDict2:classInfo];
NSLog(@"maxModel = %@",class1);
4)模型轉(zhuǎn)換的結(jié)果如下

<h3 id="4"></h3>
四、模型中的數(shù)組中裝著模型
1. 思路
數(shù)組中裝著模型,就是數(shù)組中的元素是一個(gè)字典。而這個(gè)字典對(duì)應(yīng)著一個(gè)模型。在for循環(huán)數(shù)組的時(shí)候得到一個(gè)個(gè)字典,但是卻不知道這個(gè)字典對(duì)應(yīng)的模型是什么,所以需要告訴賦值的地方,數(shù)組中裝的到底是什么模型,即模型的名稱(chēng)。
2. 代碼
這里代碼接著上面第三節(jié)的代碼使用
1)在NSObject的分類(lèi)NSObject+DictionaryToModel中添加 數(shù)組中包含模型名稱(chēng)的方法
在NSObject+DictionaryToModel.h文件中
+ (NSDictionary *)arrayContainModelClass;
在NSObject+DictionaryToModel.m文件中
+ (instancetype)modelWithDict2:(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 *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
NSString *key = [ivarName substringFromIndex:1];
id value = dict[key];
if (!value) {
NSDictionary *customKeyDict = [self modelCustomPropertyMapper];
NSString *customKey = customKeyDict[key];
value = dict[customKey];
}
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(ivarType);
if (modelClass) {
value = [modelClass modelWithDict2:value];
}
}
if ([value isKindOfClass:[NSArray class]]) {
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 轉(zhuǎn)換成id類(lèi)型,就能調(diào)用任何對(duì)象的方法
id idSelf = self;
// 獲取數(shù)組中字典對(duì)應(yīng)的模型
NSString *type = [idSelf arrayContainModelClass][key];
if (type) {
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel modelWithDict2:dict];
if (model) {
[arrM addObject:model];
} else {
//如果數(shù)組中的某個(gè)元素并不是個(gè)字典,則不做解析
[arrM addObject:dict];
}
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
//這里放一個(gè)空的字典,真正實(shí)現(xiàn)這個(gè)方法的地方是在模型中,模型中會(huì)將此方法重寫(xiě)
+ (NSDictionary *)arrayContainModelClass {
return @{};
}
2) 定義一個(gè)ZClass模型,其屬性具體如下:
ZClass.h文件中,包含了Student模型。
#import <Foundation/Foundation.h>
#import "Student.h"
@interface ZClass : NSObject
@property (nonatomic, strong) Student *student;
@property (nonatomic, strong) NSArray *item; //item中包含了Student類(lèi)
@property (nonatomic, strong) NSDictionary *dict;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property (nonatomic, copy) NSString *header;
@end
ZClass.m文件中
#import "ZClass.h"
#import "NSObject+DictionaryToModel.h"
@implementation ZClass
+ (NSDictionary *)arrayContainModelClass {
return @{@"item" : @"Student"};
}
@end
3)調(diào)用+ (instancetype)modelWithDict2:(NSDictionary *)dict
NSDictionary *classInfo = @{@"student" : studentInfo,
@"title" : @"Math",
@"subtitle" : @"Global",
@"header" : @"Shanghai",
@"dict" : studentInfo,
@"item" : @[studentInfo,studentInfo,@"whatever"]};
ZClass *class1 = [ZClass modelWithDict2:classInfo];
NSLog(@"maxModel = %@",class1);
4)模型轉(zhuǎn)換的結(jié)果如下
