iOS 字典轉(zhuǎn)模型 runtime實(shí)現(xiàn)

寫(xiě)在前面的話(huà)

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

  1. 添加:屬性名映射到字典中對(duì)應(yīng)的key的方法,如id -> ID;
  2. 修復(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é)果

image1.png

從上圖可以看出

  • 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é)果如下

image2.png

<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é)果如下

image3.png

<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é)果如下

image4.png

Github 傳送門(mén)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容