模型屬性,一般須要跟字典中的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
外界使用:服務器
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
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必需要保證:模型中屬性名要跟字典中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
KVC的底層實現:atom
setValuesForKeysWithDictionary:遍歷字典中全部key,去模型中查找對應的屬性,把值給模型屬性賦值。url
//[s setValuesForKeysWithDictionary:dict];底層實現: [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [s setValue:obj forKey:key]; }];
[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都寫入模型了,只須要寫咱們想要的數據。
剛剛介紹了字典轉模型的第一種方式---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
模型中只須要保留想要的屬性名便可。
開發中可能會遇到,二級轉換:若是字典中還有字典,也須要把對應的字典轉換成模型,那麼就須要二級轉換
。
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