iOS開發中,網絡請求得到json轉化為字典,然后字典轉化為模型,這是很普遍要做的事。成型的第三方框架也有很多,前段時間比較火的YYKit中的YYMoel對各大這方面的框架包括JsonModel,MjExtension等效率都有所對比。但是授之以魚不如授之以漁,有時候我們僅僅想要的就是字典轉模型而已,簡單,可控,可自定義。今天,小編我提供了一下自己的解決方案。
先來一波思路分析。
后臺返回的數據json(xml很少人在用了吧)的類型的數據格式有對象和數組
, 字符串
,數字
,布爾
,null
,使用系統自帶的NSJSONSerialization
得到字典,會將對象轉化為NSDictionary對象,數組轉化為NSArray對象,字符串轉化為NSString對象,數字和布爾類型轉化為NSValue對象或者子類NSNumber對象,null轉化為NSNull對象。json中的null這個就需要小心了。java的后臺程序可能是直接將model轉化為json,當對象沒初始化為null時,json就會出現null,而不是應該有的{}。null會轉化為NSNull,但是我們認為他是對象類型,轉化為了NSDictionary對象,然后調用了objectForkey,就會報unRecognized selector exception
使程序崩潰。關于這點,我曾經和做后臺的同事爭吵過,說:你既然定義json中某key的值是對象類型,為空你也要傳“{}”啊({}會轉化為空字典類型),為什么傳null。他們爭論到:從數據庫中查不到,就沒必要初始化model對象,轉化為json也就會為null。我直接無語了。
直接上代碼吧。注釋還是蠻清晰的,記得不要忘記把那兩個“安全設置”加上,不然,碰到手誤,忘記定義的屬性,程序又該崩潰了。setNilValueForKey:
這個不常用,是定義assign類型的屬性給它set nil才會觸發。加上也不多。
- (instancetype)initWithDic:(NSDictionary*)dic
{
if (!dic || ![dic isKindOfClass:[NSDictionary class]]) {
return nil;
}
if (self = [super init]) {
for (NSString *key in [dic allKeys]) {
id value = dic[key];
//1.處理對象類型和數組類型
if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
[self setValue:value forKeyPath:key];
}
//2.處理空類型:防止出現unRecognized selector exception
else if ([value isKindOfClass:[NSNull class]]) {
// [self setValue:nil forKey:key];
}
//3.處理其他類型:包括數字,字符串,布爾,全部使用NSString來處理
else{
[self setValue:[NSString stringWithFormat:@"%@",value] forKeyPath:key];
}
}
}
return self;
}
#pragma mark KVC 安全設置
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@"%s",__func__);
}
- (void)setNilValueForKey:(NSString *)key
{
NSLog(@"%s",__func__);
}
JSONModel有一個好處,就是我們在po或者log打印model對象的時候回直接展示他的屬性值。其實就是重寫description
方法而已。我們也來一波自定義:
#pragma mark po或者打印時打出內部信息
-(NSString *)description
{
NSMutableString* text = [NSMutableString stringWithFormat:@"<%@> \n", [self class]];
NSArray* properties = [self filterPropertys];
[properties enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* key = (NSString*)obj;
id value = [self valueForKey:key];
NSString* valueDescription = (value)?[value description]:@"(null)";
if ( ![value respondsToSelector:@selector(count)] && [valueDescription length]>60 ) {
valueDescription = [NSString stringWithFormat:@"%@...", [valueDescription substringToIndex:59]];
}
valueDescription = [valueDescription stringByReplacingOccurrencesOfString:@"\n" withString:@"\n "];
[text appendFormat:@" [%@]: %@\n", key, valueDescription];
}];
[text appendFormat:@"</%@>", [self class]];;
return text;
}
方法調用了[self filterPropertys]
獲本類的所有屬性。這個用到了所謂的“高大上”的objc的runtime中方法了。先來一波包含頭文件#import <objc/runtime.h>
。然后在上代碼。
#pragma mark 獲取一個類的屬性列表
- (NSArray *)filterPropertys
{
NSMutableArray* props = [NSMutableArray array];
unsigned int count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for(int i = 0; i < count; i++){
objc_property_t property = properties[i];
const char* char_f =property_getName(property);
NSString *propertyName = [NSString stringWithUTF8String:char_f];
[props addObject:propertyName];
// NSLog(@"name:%s",property_getName(property));
// NSLog(@"attributes:%s",property_getAttributes(property));
}
free(properties);
return props;
}
最后。字典轉模型,你是轉了,那模型轉字典呢?作為一個實用主義的程序員,如果不是有需求用到了,我才不去想這個的,多燒腦子啊??。開發時,有時候需要提交數據給后臺,由于網絡請求的封裝,只需要傳一個字典對象過去就行。如果需要把一個model對象所有屬性都作為參數提交,那么就需要吧model轉化為字典類型。方法如下:
#pragma mark 模型中的字符串類型的屬性轉化為字典
-(NSDictionary*)modelStringPropertiesToDictionary
{
NSArray* properties = [self filterPropertys];
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
[properties enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* key = (NSString*)obj;
id value = [self valueForKey:key];
if ([value isKindOfClass:[NSString class]]) {
NSString* va = (NSString*)value;
if (va.length > 0) {
[dic setObject:value forKey:key];
}
}
}];
return dic;
}
將以上代碼封裝一個BaseModel類,所有model類繼承它。
以上只是根據自己的所學加以運用而已,一千個讀者就有一千個哈姆雷特。好多東西不是沒法解決,只是暫時不知道解決的辦法而已。這篇文章是自己的所學的一個總結,希望對讀者有所幫助。