1较曼、簡介字典轉(zhuǎn)模型
在日常開發(fā)中磷斧,將網(wǎng)絡(luò)請求中獲取的 JSON 數(shù)據(jù)轉(zhuǎn)為數(shù)據(jù)模型,是我們開發(fā)中必不可少的操作捷犹。
通常我們會選用諸如 YYModel
弛饭、JSONModel
或者 MJExtension
等第三方框架來實(shí)現(xiàn)這一過程。這些框架實(shí)現(xiàn)原理的核心就是 Runtime
和 KVC
萍歉,以及 Getter / Setter
侣颂。
實(shí)現(xiàn)的大體思路如下:借助 Runtime
可以動態(tài)獲取成員列表
的特性,遍歷模型中所有屬性枪孩,然后以獲取到的屬性名為 key
憔晒,在 JSON 字典中尋找對應(yīng)的值 value
;再使用 KVC 或直接調(diào)用 Getter / Setter
將每一個(gè)對應(yīng) value
賦值給模型蔑舞,就完成了字典轉(zhuǎn)模型的目的拒担。
需求:將服務(wù)器返回的 JSON 字典轉(zhuǎn)為數(shù)據(jù)模型。
先準(zhǔn)備一份待解析的 JSON 數(shù)據(jù)攻询,內(nèi)容如下:
{
"id": "123412341234",
"name": "心里只有你",
"age": "18",
"weight": 120,
"address": {
"country": "中國",
"province": "北京"
},
"courses": [
{
"name": "Chinese",
"desc": "語文課"
},
{
"name": "Math",
"desc": "數(shù)學(xué)課"
},
{
"name": "English",
"desc": "英語課"
}
]
}
從這份 JSON 中可以看出从撼,字典中取值除了字符串
之外,還有數(shù)組
和字典
蜕窿。那么在將字典轉(zhuǎn)換成數(shù)據(jù)模型的時(shí)候谋逻,就要考慮 模型嵌套模型
、模型嵌套模型數(shù)組
的情況了桐经。
2毁兆、具體實(shí)現(xiàn)如下:
2.1、 創(chuàng)建模型
經(jīng)過分析阴挣,我們總共需要三個(gè)模型: XXStudentModel气堕、XXAdressModel、XXCourseModel。
/********************* XXStudentModel.h 文件 *********************/
#import <Foundation/Foundation.h>
#import "NSObject+XXModel.h"
@class XXAdressModel, XXCourseModel;
@interface XXStudentModel : NSObject <XXModel>
/* 姓名 */
@property (nonatomic, copy) NSString *name;
/* 學(xué)生號 id */
@property (nonatomic, copy) NSString *uid;
/* 年齡 */
@property (nonatomic, assign) NSInteger age;
/* 體重 */
@property (nonatomic, assign) NSInteger weight;
/* 地址(嵌套模型) */
@property (nonatomic, strong) XXAdressModel *address;
/* 課程(嵌套模型數(shù)組) */
@property (nonatomic, strong) NSArray *courses;
@end
/********************* XXStudentModel.m 文件 *********************/
#import "XXStudentModel.h"
#import "XXCourseModel.h"
@implementation XXStudentModel
+ (NSDictionary *)modelContainerPropertyGenericClass {
//需要特別處理的屬性
return @{
@"courses" : [XXCourseModel class],
@"uid" : @"id"
};
}
@end
/********************* XXAdressModel.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXAdressModel : NSObject
/* 國籍 */
@property (nonatomic, copy) NSString *country;
/* 省份 */
@property (nonatomic, copy) NSString *province;
/* 城市 */
@property (nonatomic, copy) NSString *city;
@end
/********************* XXAdressModel.m 文件 *********************/
#import "XXAdressModel.h"
@implementation XXAdressModel
@end
/********************* XXCourseModel.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXCourseModel : NSObject
/* 課程名 */
@property (nonatomic, copy) NSString *name;
/* 課程介紹 */
@property (nonatomic, copy) NSString *desc;
@end
/********************* XXCourseModel.m 文件 *********************/
#import "XXCourseModel.h"
@implementation XXCourseModel
@end
2.2茎芭、 在 NSObject 分類中實(shí)現(xiàn)字典轉(zhuǎn)模型
NSObject+XXModel.h揖膜、NSObject+XXModel.m 就是我們用來解決字典轉(zhuǎn)模型所創(chuàng)建的分類,協(xié)議中的 + (NSDictionary *)modelContainerPropertyGenericClass 方法用來告訴分類特殊字段的處理規(guī)則梅桩,比如 id --> uid壹粟。
/********************* NSObject+XXModel.h 文件 *********************/
#import <Foundation/Foundation.h>
// XXModel 協(xié)議
@protocol XXModel <NSObject>
@optional
// 協(xié)議方法:返回一個(gè)字典,表明特殊字段的處理規(guī)則
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
@end;
@interface NSObject (XXModel)
// 字典轉(zhuǎn)模型方法
+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary;
@end
/********************* NSObject+XXModel.m 文件 *********************/
#import "NSObject+XXModel.h"
#import <objc/runtime.h>
@implementation NSObject (XXModel)
+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary {
// 創(chuàng)建當(dāng)前模型對象
id object = [[self alloc] init];
unsigned int count;
// 獲取當(dāng)前對象的屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
// 遍歷 propertyList 中所有屬性宿百,以其屬性名為 key趁仙,在字典中查找 value
for (unsigned int i = 0; i < count; i++) {
// 獲取屬性
objc_property_t property = propertyList[i];
const char *propertyName = property_getName(property);
NSString *propertyNameStr = [NSString stringWithUTF8String:propertyName];
// 獲取 JSON 中屬性值 value
id value = [dictionary objectForKey:propertyNameStr];
// 獲取屬性所屬類名
NSString *propertyType;
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
switch (attrs[i].name[0]) {
case 'T': { // Type encoding
if (attrs[i].value) {
propertyType = [NSString stringWithUTF8String:attrs[i].value];
// 去除轉(zhuǎn)義字符:@\"NSString\" -> @"NSString"
propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 去除 @ 符號
propertyType = [propertyType stringByReplacingOccurrencesOfString:@"@" withString:@""];
}
} break;
default: break;
}
}
// 對特殊屬性進(jìn)行處理
// 判斷當(dāng)前類是否實(shí)現(xiàn)了協(xié)議方法,獲取協(xié)議方法中規(guī)定的特殊屬性的處理方式
NSDictionary *perpertyTypeDic;
if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){
perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil];
}
// 處理:字典的 key 與模型屬性不匹配的問題垦页,如 id -> uid
id anotherName = perpertyTypeDic[propertyNameStr];
if(anotherName && [anotherName isKindOfClass:[NSString class]]){
value = dictionary[anotherName];
}
// 處理:模型嵌套模型的情況
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(propertyType);
if (modelClass != nil) {
// 將被嵌套字典數(shù)據(jù)也轉(zhuǎn)化成Model
value = [modelClass xx_modelWithDictionary:value];
}
}
// 處理:模型嵌套模型數(shù)組的情況
// 判斷當(dāng)前 value 是一個(gè)數(shù)組雀费,而且存在協(xié)議方法返回了 perpertyTypeDic
if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) {
Class itemModelClass = perpertyTypeDic[propertyNameStr];
//封裝數(shù)組:將每一個(gè)子數(shù)據(jù)轉(zhuǎn)化為 Model
NSMutableArray *itemArray = @[].mutableCopy;
for (NSDictionary *itemDic in value) {
id model = [itemModelClass xx_modelWithDictionary:itemDic];
[itemArray addObject:model];
}
value = itemArray;
}
// 使用 KVC 方法將 value 更新到 object 中
if (value != nil) {
[object setValue:value forKey:propertyNameStr];
}
}
free(propertyList);
return object;
}
@end
2.3、 測試代碼
- (void)parseJSON {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Student" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
// 讀取 JSON 數(shù)據(jù)
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@",json);
// JSON 字典轉(zhuǎn)模型
XXStudentModel *student = [XXStudentModel xx_modelWithDictionary:json];
NSLog(@"student.uid = %@", student.uid);
NSLog(@"student.name = %@", student.name);
for (unsigned int i = 0; i < student.courses.count; i++) {
XXCourseModel *courseModel = student.courses[i];
NSLog(@"courseModel[%d].name = %@ .desc = %@", i, courseModel.name, courseModel.desc);
}
}
當(dāng)然痊焊,如若需要考慮緩存機(jī)制盏袄、性能問題、對象類型檢查等薄啥,建議還是使用例如 YYModel 之類的知名第三方框架辕羽,或者自己造輪子。