runtime從入門到精通(八)—— 使用runtime實現字典轉模型
阿新 • • 發佈:2019-02-01
由於文章篇幅長度原因,筆者單獨用一篇文章來介紹使用runtime來實現字典轉模型。檢視完整的runtime在實際開發中的使用點選連結 ——> runtime從入門到精通(六)—— runtime在實際開發中的應用
字典轉模型的第一步:設計模型
- 模型屬性,通常需要跟字典中的key一一對應
- 問題:一個一個的生成模型屬性,很慢?
- 需求:能不能自動根據一個字典,生成對應的屬性。
- 解決:提供一個分類,專門根據字典生成對應的屬性字串。
@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
字典轉模型的第二步(方式1):KVC的方式來字典轉模型(之前使用的方式)
+ (instancetype)statusWithDict:(NSDictionary *)dict
{
Status *status = [[self alloc] init];
[status setValuesForKeysWithDictionary:dict];
return status;
}
@end
KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。如果不一致,就會呼叫[ setValue:forUndefinedKey:],報key找不到的錯。
分析:模型中的屬性和字典的key不一一對應,系統就會呼叫setValue:forUndefinedKey:報錯。
解決:重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋,就能繼續使用KVC,字典轉模型了。
字典轉模型的第二步(方式1):利用Runtime來字典轉模型
- 思路:利用執行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查詢key,取出對應的值,給模型的屬性賦值。
- 步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類轉。
實現程式碼:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 解析Plist檔案
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
// 獲取字典陣列
NSArray *dictArr = statusDict[@"statuses"];
// 自動生成模型的屬性字串
// [NSObject resolveDict:dictArr[0][@"user"]];
_statuses = [NSMutableArray array];
// 遍歷字典陣列
for (NSDictionary *dict in dictArr) {
Status *status = [Status modelWithDict:dict];
[_statuses addObject:status];
}
// 測試資料
NSLog(@"%@ %@",_statuses,[_statuses[0] user]);
}
@end
@implementation NSObject (Model)
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
// 思路:遍歷模型中所有屬性-》使用執行時
// 0.建立對應的物件
id objc = [[self alloc] init];
// 1.利用runtime給物件中的成員屬性賦值
// class_copyIvarList:獲取類中的所有成員屬性
// Ivar:成員屬性的意思
// 第一個引數:表示獲取哪個類中的成員屬性
// 第二個引數:表示這個類有多少成員屬性,傳入一個Int變數地址,會自動給這個變數賦值
// 返回值Ivar *:指的是一個ivar陣列,會把所有成員屬性放在一個數組中,通過返回的陣列就能全部獲取到。
/* 類似下面這種寫法
Ivar ivar;
Ivar ivar1;
Ivar ivar2;
// 定義一個ivar的陣列a
Ivar a[] = {ivar,ivar1,ivar2};
// 用一個Ivar *指標指向陣列第一個元素
Ivar *ivarList = a;
// 根據指標訪問陣列第一個元素
ivarList[0];
*/
unsigned int count;
// 獲取類中的所有成員屬性
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]]) {
// 字典轉模型
// 獲取模型的類物件,呼叫modelWithDict
// 模型的類名已知,就是成員屬性的型別
// 獲取成員屬性型別
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 生成的是這種@"@\"User\"" 型別 -》 @"User" 在OC字串中 \" -> ",\是轉義的意思,不佔用字元
// 裁剪型別字串
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
個人總結:
實現字典和模型的自動轉換:首先要根據模型的實現模型。。。。。。。
(核心就是可以遍歷出字典中的每個屬性,json解析中大牛框架都用了這個特性,包括MJEXtension,YYModel,jsonModel都是將json轉換為字典,再遍歷字典中的每個屬性來進行modle的轉換)。
基本上主流的json 轉model 都少不了,使用執行時動態獲取屬性的屬性名的方法,來進行字典轉模型替換,字典轉模型效率最高的(耗時最短的)的是KVC,其他的字典轉模型是在KVC 的key 和Value 做處理,動態的獲取json 中的key 和value ,當然轉換的過程中,第三方框架需要做一些判空
啊,鑲嵌
的邏輯處理, 再進行KVC
轉模型.
無論JsonModle,YYKIt,MJextension 都少不了[xx setValue:value forKey:key];
這句程式碼的,不信可以去搜,這是字典轉模型的核心方法。