教你使用runtime寫一個簡單的字典轉模型的工具類

一.自動生成屬性的分類

模型屬性,一般須要跟字典中的key一一對應。從服務器獲得的數據太雜?數據太多?寫成plist文件後一個個對照填寫屬性,太繁瑣?那麼我麼能夠嘗試寫一個分類來自動打印出全部屬性。
    •    需求:能不能根據一個字典,自動生成對應的屬性。
    •    解決:提供一個分類,專門根據字典生成對應的屬性字符串。數組

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface NSDictionary (PropertyCode)
 4 
 5 // 生成屬性代碼
 6 - (void)createPropetyCode;
 7 @end
 8 
 9 @implementation NSDictionary (PropertyCode)
10 // 私有API:真實存在,可是蘋果沒有暴露出來,不給你用
11 // isKindOfClass:判斷下是不是當前類或者子類
12 // 自動生成屬性代碼
13 - (void)createPropetyCode
14 {
15     NSMutableString *codes = [NSMutableString string];
16     // 遍歷字典
17     [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull value, BOOL * _Nonnull stop) {
18         NSString *code = nil;
19         
20         if ([value isKindOfClass:[NSString class]]) {// 注:NSString *筆者喜歡用strong,若想使用copy可修改字符串
21           code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",key];
22         } else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
23             code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",key];
24         } else if ([value isKindOfClass:[NSNumber class]]) {
25              code = [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger %@;",key];
26         } else if ([value isKindOfClass:[NSArray class]]) {
27             code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",key];
28         } else if ([value isKindOfClass:[NSDictionary class]]) {
29             code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",key];
30         }
31         
32         // 拼接字符串
33         [codes appendFormat:@"\n%@\n",code];
34 
35     }];
36     
37     NSLog(@"%@",codes);
38 }
39 @end
分類NSDictionary+PropertyCode

外界使用:服務器

 1 #import "ViewController.h"
 2 #import "Status.h"
 3 #import "NSDictionary+PropertyCode.h"
 4 /*
 5     plist:
 6     字典
 7     字典轉模型
 8  */
 9 
10 @interface ViewController ()
11 
12 @end
13 
14 @implementation ViewController
15 
16 - (void)viewDidLoad {
17     [super viewDidLoad];
18     
19     // 解析Plist
20     // 獲取文件全路徑
21     NSString *fileName = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
22     // 獲取字典
23     NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:fileName];
24     
25     // 設計模型-定義屬性
26     // 自動生成屬性代碼
27     [dict createPropetyCode];
28 }
29 @end
ViewController

status.plistapp

控制檯打印信息:ide

2016-04-15 10:43:34.959 Runtime(自動生成屬性)[38837:1398902]
@property (nonatomic, assign) BOOL aaa;

@property (nonatomic, assign) NSInteger reposts_count;

@property (nonatomic, copy) NSString *source;

@property (nonatomic, strong) NSArray *pic_urls;

@property (nonatomic, copy) NSString *created_at;

@property (nonatomic, assign) NSInteger attitudes_count;

@property (nonatomic, copy) NSString *idstr;

@property (nonatomic, copy) NSString *text;

@property (nonatomic, assign) NSInteger comments_count;

@property (nonatomic, strong) NSDictionary *user;
控制檯打印信息

二.字典轉模型KVC實現

KVC必需要保證:模型中屬性名要跟字典中key一一對應。

需求:開發中,一般後臺會給你不少數據,可是並非每一個數據都有用,這些沒有用的數據,需不須要保存到模型中?post

使用KVC把字典轉成模型:this

#import <Foundation/Foundation.h>

@interface Status : NSObject

// 字典中有多少key,模型就有多少個屬性

// 自動生成屬性 -> 依賴字典

@property (nonatomic, assign) BOOL aaa;

@property (nonatomic, strong) NSString *source;

@property (nonatomic, assign) NSInteger reposts_count;

@property (nonatomic, strong) NSArray *pic_urls;

@property (nonatomic, strong) NSString *created_at;

@property (nonatomic, assign) NSInteger attitudes_count;

@property (nonatomic, strong) NSString *idstr;

@property (nonatomic, strong) NSString *text;

@property (nonatomic, assign) NSInteger comments_count;

@property (nonatomic, strong) NSDictionary *user;

// 定義屬性 -> 設計模型
+ (instancetype)statusWithDict:(NSDictionary *)dict;
@end

@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
    // 建立模型
    Status *s = [[self alloc] init];
    
    // 字典value轉模型屬性保存
    [s setValuesForKeysWithDictionary:dict];

    return s;
}
@end
Status模型

KVC的底層實現:atom

setValuesForKeysWithDictionary:遍歷字典中全部key,去模型中查找對應的屬性,把值給模型屬性賦值。url

    //[s setValuesForKeysWithDictionary:dict];底層實現:

    [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

        [s setValue:obj forKey:key];
    }];
setValuesForKeysWithDictionary:底層實現

 [s setValue:dict[@"source"] forKey:@"source"];spa

原理:
 1.首先會去模型中查找有沒有【setSource方法】,若是有 直接調用set方法 [s setSource:dict[@"source"]];
 2.不然,去模型中查找有沒有【source屬性】,若是有 source = dict[@"source"]
 3.不然,去模型中查找有沒有【_source屬性】,若是有 _source = dict[@"source"]
 4.再不然,調用對象的 【setValue:forUndefinedKey:】直接報錯設計

使用KVC最大的弊端:模型中屬性名要跟字典中key一一對應,不對應就會報錯:

 2016-04-15 11:11:29.591 Runtime(字典轉模型KVC實現)[39288:1411792] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Status 0x7f9cb9f19680> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key source.'

那麼,【重寫setValue:forUndefinedKey:方法】,屏蔽系統報錯就能解決模型中屬性名要跟字典中key必須對應的問題。

@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict{
    // 建立模型
    Status *s = [[self alloc] init];
    
    // 字典value轉模型屬性保存
    [s setValuesForKeysWithDictionary:dict];
   
    return s;
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
@end

這樣就不用把字典中的全部key都寫入模型了,只須要寫咱們想要的數據。

三.Runtime字典轉模型一級轉換

剛剛介紹了字典轉模型的第一種方式---KVC,下面進入主題。

•    字典轉模型的方式二:Runtime
    ◦    思路:利用運行時,【遍歷模型中全部屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值】。
    ◦    步驟:提供一個NSObject分類,專門字典轉模型,之後全部模型均可以經過這個分類來轉。

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface NSObject (Model)
 4 
 5 + (instancetype)modelWithDict:(NSDictionary *)dict;
 6 
 7 @end
 8 
 9 
10 #import <objc/message.h>
11 
12 //class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount)
13 //這個方法只能獲取【屬性】列表(大括號裏的成員變量不能獲取)
14 
15 @implementation NSObject (Model)
16 + (instancetype)modelWithDict:(NSDictionary *)dict
17 {
18     id objc = [[self alloc] init];
19     
20     // 獲取成員變量列表
21     // 第一個參數class:獲取哪一個類成員變量列表
22     // 第二個參數count:成員變量總數
23     unsigned int count = 0;
24 
25     // 成員變量數組 指向數組第0個元素
26     Ivar *ivarList = class_copyIvarList(self, &count);
27 
28     // 遍歷全部成員變量
29     for (int i = 0; i < count; i++) {
30         // 獲取成員變量
31         Ivar ivar = ivarList[i];
32         // 獲取成員變量名稱
33         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
34  
35         // 成員變量名稱轉換key,去掉成員變量名稱前面的下劃線
36         NSString *key = [ivarName substringFromIndex:1];
37         
38         // 從字典中取出對應value
39         id value = dict[key];
40         
41         // 給模型中屬性賦值
42         [objc setValue:value forKey:key];
43     }
44     return objc;
45 }
46 @end
NSObject+Model分類

模型中只須要保留想要的屬性名便可。

四.Runtime字典轉模型二級轉換

開發中可能會遇到,二級轉換:若是字典中還有字典,也須要把對應的字典轉換成模型,那麼就須要二級轉換

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface NSObject (Model)
 4 
 5 + (instancetype)modelWithDict:(NSDictionary *)dict;
 6 
 7 @end
 8 
 9 #import <objc/message.h>
10 
11 @implementation NSObject (Model)
12 + (instancetype)modelWithDict:(NSDictionary *)dict
13 {
14     id objc = [[self alloc] init];
15 
16     unsigned int count = 0;
17 
18     // 成員變量數組 指向數組第0個元素
19     Ivar *ivarList = class_copyIvarList(self, &count);
20 
21     // 遍歷全部成員變量
22     for (int i = 0; i < count; i++) {
23         
24         // 獲取成員變量 user
25         Ivar ivar = ivarList[i];
26         // 獲取成員變量名稱
27         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
28         
29         // 獲取成員變量類型(後續判斷是否進行二次轉換)
30         NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
31         NSLog(@"%@",type);
32         // @"@"User"" -> @"User"
33         type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
34         type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
35        
36         // 成員變量名稱轉換key
37         NSString *key = [ivarName substringFromIndex:1];
38         
39         // 從字典中取出對應value dict[@"user"] -> 字典
40         id value = dict[key];
41         
42         // 二級轉換
43         // 而且是自定義類型,才須要轉換
44         if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典,而且不包含NS才須要轉換
45            
46             //獲取類名
47             Class className = NSClassFromString(type);
48             
49             // 字典轉模型
50             value = [className modelWithDict:value];
51         }
52         
53         // 給模型中屬性賦值 key:user value:字典 ---> 模型
54         if (value) {
55             [objc setValue:value forKey:key];
56         }
57     }
58     return objc;
59 }
60 @end
相關文章
相關標籤/搜索