寫在前面的話
這篇文章的通過runtime實現字典轉模型是參考(抄襲)iOS 模式詳解—「runtime面試、工作」看我就 ?? 了 _.中runtime 字典轉模型,并且在此基礎上做了以下擴展:
- 添加:屬性名映射到字典中對應的key的方法,如id -> ID;
- 修復:當模型中的數組中不全是某一個模型的時候,會引起崩潰的問題。如數組中有8個元素,其中7個是模型,還有一個是字符串;
Github 傳送門
需要考慮以下三種情況
- 當字典中的key和模型的屬性匹配不上;
- 模型中嵌套模型(模型的屬性是另外一個模型對象);
- 模型中的數組中裝著模型(數組中的元素是一個模型)。
一、使用runtime將字典轉成模型
1. 思路
使用runtime遍歷出模型中的所有屬性,根據模型中屬性,去字典中取出對應的value給模型屬性賦值
2. 代碼
1) 定義一個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)對NSObject擴展一個分類NSObject+DictionaryToModel
NSObject+DictionaryToModel.h文件
+ (instancetype)modelWithDict:(NSDictionary *)dict;
NSObject+DictionaryToModel.m文件,一定要導入<objc/message.h>
#import "NSObject+DictionaryToModel.h"
#import <objc/message.h>
@implementation NSObject (DictionaryToModel)
/*
* 根據模型中屬性,去字典中取出對應的value并賦值給模型的屬性
* 遍歷取出所有屬性
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
//1. 創建對應的對象
id objc = [[self alloc] init];
//2. 利用runtime給對象中的屬性賦值
/*
Ivar: 成員變量;
class_copyIvarList(): 獲取類中的所有成員變量;
第一個參數:表示獲取哪個類的成員變量;
第二個參數:表示這個類有多少成員變量;
返回值Ivar *:指的是一個ivar數組,會把所有成員變量放在一個數組中,通過返回數組就全部獲取到。
count: 成員變量個數
*/
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(去掉 _ ,從第一個角標開始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根據成員屬性名去字典中查找對應的value
id value = dict[key];
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
@end
3)調用+ (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)模型的轉換結果
從上圖可以看出
-
student.ID
沒有賦值成功,是因為在數據中沒有ID
這個key(Objective-C 中id
是保留字,所以student的屬性這里只能用ID
) - 對于這種模型屬性名和數據中key不對應的問題,接下來會講如何解決。
<h3 id="2"></h3>
二、當字典中的key和模型的屬性匹配不上
1. 思路
如果字典中的key和模型的屬性匹配不上,可以做一個映射。將屬性名映射到字典中對應的key上
2. 代碼
這里代碼接著上面的代碼使用
1)在NSObject的分類NSObject+DictionaryToModel中添加映射方法
NSObject+DictionaryToModel.h文件中
+ (NSDictionary *)modelCustomPropertyMapper;
NSObject+DictionaryToModel.m文件中
#import "NSObject+DictionaryToModel.h"
#import <objc/message.h>
@implementation NSObject (DictionaryToModel)
/*
* 根據模型中屬性,去字典中取出對應的value并賦值給模型的屬性
* 遍歷取出所有屬性
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) {
return nil;
}
//1. 創建對應的對象
id objc = [[self alloc] init];
//2. 利用runtime給對象中的屬性賦值
/*
Ivar: 成員變量;
class_copyIvarList(): 獲取類中的所有成員變量;
第一個參數:表示獲取哪個類的成員變量;
第二個參數:表示這個類有多少成員變量;
返回值Ivar *:指的是一個ivar數組,會把所有成員變量放在一個數組中,通過返回數組就全部獲取到。
count: 成員變量個數
*/
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(去掉 _ ,從第一個角標開始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根據成員屬性名去字典中查找對應的value
id value = dict[key];
//如果通過屬性名取不到對應的value,則更換屬性名對應的映射名來取值
if (!value) {
NSDictionary *customKeyDict = [self modelCustomPropertyMapper];
NSString *customKey = customKeyDict[key];
value = dict[customKey];
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
//這里放一個空的字典,真正實現這個映射方法的地方是在模型中,模型中會將此方法重寫
+ (NSDictionary *)modelCustomPropertyMapper {
return @{};
}
@end
2)在模型中實現映射方法
//重寫NSObject+DictionaryToModel分類中的映射方法
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"ID" : @"id"};
}
3)模型轉換的結果如下
<h3 id="3"></h3>
三、模型中嵌套模型
1. 思路
模型中嵌套模型就是字典中嵌套字典,當給模型的模型賦值的時候,再調用一次字典轉模型就可以了。其實就是遞歸調用
2. 代碼
1) 定義一個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的分類NSObject+DictionaryToModel中
完善一下+ (instancetype)modelWithDict:(NSDictionary *)dict
方法,這里我寫在+ (instancetype)modelWithDict2:(NSDictionary *)dict
中。
NSObject+DictionaryToModel.h文件
+ (instancetype)modelWithDict2:(NSDictionary *)dict;
NSObject+DictionaryToModel.m文件,一定要導入<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)];
// 獲取成員變量類型
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是一個字典,并且其類型是自定義對象才需要轉換。不是OC中的數據類型,如:NSString, NSArray, NSDictionary, NSMutableArray, NSMutableDictionary, NSNumber等
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(ivarType);
if (modelClass) {
//如果modelClass存在,則進入遞歸調用
value = [modelClass modelWithDict2:value];
}
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
3)調用+ (instancetype)modelWithDict2:(NSDictionary *)dict
NSDictionary *classInfo = @{@"student" : studentInfo,
@"title" : @"Math",
@"subtitle" : @"Global",
@"header" : @"Shanghai"};
ZClass *class1 = [ZClass modelWithDict2:classInfo];
NSLog(@"maxModel = %@",class1);
4)模型轉換的結果如下
<h3 id="4"></h3>
四、模型中的數組中裝著模型
1. 思路
數組中裝著模型,就是數組中的元素是一個字典。而這個字典對應著一個模型。在for循環數組的時候得到一個個字典,但是卻不知道這個字典對應的模型是什么,所以需要告訴賦值的地方,數組中裝的到底是什么模型,即模型的名稱。
2. 代碼
這里代碼接著上面第三節的代碼使用
1)在NSObject的分類NSObject+DictionaryToModel中添加 數組中包含模型名稱的方法
在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)]) {
// 轉換成id類型,就能調用任何對象的方法
id idSelf = self;
// 獲取數組中字典對應的模型
NSString *type = [idSelf arrayContainModelClass][key];
if (type) {
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數組,生成模型數組
for (NSDictionary *dict in value) {
// 字典轉模型
id model = [classModel modelWithDict2:dict];
if (model) {
[arrM addObject:model];
} else {
//如果數組中的某個元素并不是個字典,則不做解析
[arrM addObject:dict];
}
}
// 把模型數組賦值給value
value = arrM;
}
}
}
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
//這里放一個空的字典,真正實現這個方法的地方是在模型中,模型中會將此方法重寫
+ (NSDictionary *)arrayContainModelClass {
return @{};
}
2) 定義一個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類
@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)調用+ (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);