iOS中字典轉模型的方法及底層原理
阿新 • • 發佈:2019-02-12
1 自動列印屬性字串分類
- 提供一個分類,專門根據字典生成對應的屬性字串。
@implementation NSObject (Property)
+ (void)PH_createPropertyCodeWithDict:(NSDictionary *)dict
{
NSMutableString *strM = [NSMutableString string];
// 遍歷字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull propertyName, id _Nonnull value, BOOL * _Nonnull stop) {
NSLog (@"%@,%@",propertyName,[value class]);
NSString *code;
if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",propertyName]
;
} else if ([value isKindOfClass:NSClassFromString (@"__NSCFNumber")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;" ,propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
;
} else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
;
}
[strM appendFormat:@"\n%@\n",code];
}];
NSLog(@"%@",strM);
}
@end
2 字典轉模型的方式一:KVC
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict
{
Status *status = [[self alloc] init];
[status setValuesForKeysWithDictionary:dict];
return status;
}
@end
- KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。
- 如果不一致,就會呼叫
[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]
報key
找不到的錯。 - 分析:模型中的屬性和字典的key不一一對應,系統就會呼叫
setValue:forUndefinedKey:
報錯。 - 解決:重寫物件的
setValue:forUndefinedKey:
,把系統的方法覆蓋,
就能繼續使用KVC,字典轉模型了。
- 如果不一致,就會呼叫
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
3 字典轉模型的方式二:Runtime
- KVC:遍歷字典中所有key,去模型中查詢有沒有對應的屬性名
- runtime:遍歷模型中所有屬性名,去字典中查詢
1.思路與步驟
<!--* 思路:利用執行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查詢key,取出對應的值,給模型的屬性賦值。-->
<!--* 步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類轉。-->
2.分類
+ (instancetype)PH_modelWithDict:(NSDictionary *)dict
{
// 建立對應類的物件
id objc = [[self alloc] init];
// runtime:遍歷模型中所有成員屬性,去字典中查詢
// 屬性定義在哪,定義在類,類裡面有個屬性列表(陣列)
// 遍歷模型所有成員屬性
// ivar:成員屬性
// class_copyIvarList:把成員屬性列表複製一份給你
// Ivar *:指向Ivar指標
// Ivar *:指向一個成員變數陣列
// class:獲取哪個類的成員屬性列表
// count:成員屬性總數
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
for (int i = 0; i < count; i++) {
// 獲取成員屬性
Ivar ivar = ivarList[i];
// 獲取成員名
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//成員屬性型別
NSString *propertyType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//獲取key
NSString *key = [propertyName substringFromIndex:1];
//獲取字典的value
id value = dict[key];
// 給模型的屬性賦值
// value:字典的值
// key:屬性名
// user:NSDictionary
//** '二級轉換'**
// 值是字典,成員屬性的型別不是字典,才需要轉換成模型
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType containsString:@"NS"]) {
// 需要字典轉換成模型
// 轉換成哪個型別
// @"@\"User\"" User
NSRange range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location + range.length];
// User\"";
range = [propertyType rangeOfString:@"\""];
propertyType = [propertyType substringFromIndex:range.location];
// 字串擷取
// 獲取需要轉換類的類物件
Class modelClass = NSClassFromString(propertyType);
if (modelClass) {
value = [modelClass PH_modelWithDict:value];
}
}
<!-- 三級轉換:NSArray中也是字典,把陣列中的字典轉換成模型.-->
// 判斷值是否是陣列
if ([value isKindOfClass:[NSArray class]]) {
//判斷對應類有沒有實現字典陣列轉模型陣列的協議
if ([self respondsToSelector:@selector(PH_arrayContainModelClass)]) {
// 轉換成id型別,就能呼叫任何物件的方法
id idSelf = self;
// 獲取陣列中字典對應的模型
NSString *type = [idSelf PH_arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典陣列,生成模型陣列
for (NSDictionary *dict in value) {
// 字典轉模型
id model = [classModel PH_modelWithDict:dict];
[arrM addObject:model];
}
// 把模型陣列賦值給value
value = arrM;
}
}
if (value) {
// kvc賦值:不能傳空
[objc setValue:value forKey:key];
}
NSLog(@"%@",key);
NSLog(@"%@,%@",propertyName,propertyType);
}
return objc;
}
3.轉換
- (void)viewDidLoad {
[super viewDidLoad];
// 解析
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *dictArr = dict[@"statuses"];
// 設計模型屬性程式碼
NSMutableArray *statuses = [NSMutableArray array];
for (NSDictionary *dict in dictArr) {
// 字典轉模型
Status *status = [Status PH_modelWithDict:dict];
[statuses addObject:status];
}
NSLog(@"%@",statuses);
}
KVC: Key Value Coding (鍵值編碼)
在iOS開發中,KVC是我們經常要使用的技術.那麼KVC有什麼作用呢?簡單列舉一下下面幾種:
- 取值和賦值(開發中基本不用)
- 獲取物件私有變數的值.(經常使用,例如UIPageContorl分頁, 設定圓點為圖片)
- 改變物件私有變數的值(經常使用)
- 簡單的字典轉模型(偶爾使用)
- 模型轉字典
- 批量取值
KVC字典轉模型的底層實現
通常我們手動將字典轉模型的話,會在模型中提供一個類方法接收一個字典,在這個方法中將字典轉換成模型,再將轉換好的模型返回.
+ (instancetype)statusWithDict:(NSDictionary *)dict { Status *status = [[self alloc] init]; //利用KVC字典轉模型 [status setValuesForKeysWithDictionary:dict]; return status; }
分析一下
[status setValuesForKeysWithDictionary:dict]
的底層實現原理+ (instancetype)statusWithDict:(NSDictionary *)dict { Status *status = [[self alloc] init]; //利用KVC字典轉模型 //[status setValuesForKeysWithDictionary:dict]; //setValuesForKeysWithDictionary:原理--遍歷字典中所有的key,去模型中查詢對應的屬性,把值給模型屬性賦值 [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { // 這行程式碼才是真正給模型的屬性賦值 [status setValue:obj forKey:key]; }]; return status; }
- KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。如果不是一一對應的話,就會報錯,仔細看一下錯誤資訊,
[<Status 0x7fd439d20a60> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key source.
是系統呼叫了setValue:forUndefinedKey:報錯.如果想解決這個問題,只需要在模型中重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋, 就能繼續使用KVC,字典轉模型了。- (void)setValue:(id)value forUndefinedKey:(NSString *)key { }
囉嗦一點KVC的setValue:forKey:
方法賦值的原理
- 首先會去模型中查詢有沒有對應key的setter方法,有就直接呼叫set方法方法賦值.
- 上一步沒有的話,去模型中查詢有沒有和key同名的屬性,有的話賦值給與key同名的屬性.
- 上一步還沒有的話,去屬性中查詢有沒有和key同名的帶下劃線的屬性,有的話直接賦值.
- 如果再沒有,那就直接呼叫物件的 setValue:forUndefinedKey:直接報錯