iOS 字典轉模型 runtime實現

寫在前面的話

這篇文章的通過runtime實現字典轉模型是參考(抄襲)iOS 模式詳解—「runtime面試、工作」看我就 ?? 了 _.中runtime 字典轉模型,并且在此基礎上做了以下擴展:

  1. 添加:屬性名映射到字典中對應的key的方法,如id -> ID;
  2. 修復:當模型中的數組中不全是某一個模型的時候,會引起崩潰的問題。如數組中有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)模型的轉換結果

image1.png

從上圖可以看出

  • 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)模型轉換的結果如下

image2.png

<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)模型轉換的結果如下

image3.png

<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);

4)模型轉換的結果如下

image4.png

Github 傳送門

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容