模型转换工具 Mantle, MJExtension, JSONModel 的使用和异同及防崩溃能力比较

前端之家收集整理的这篇文章主要介绍了模型转换工具 Mantle, MJExtension, JSONModel 的使用和异同及防崩溃能力比较前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

现在大部分的项目都需要将服务器返回的JSON数据转换为Model再使用,手动转换不仅费时费力,还写了一堆重复代码,肯定是不科学的,一般都使用相应的工具来自动转换。目前接触的字典转模型工具有三种,Mantle,MJExtension,JSONModel,虽然他们做的事情都是一样的,但是使用方法区别还是蛮大的,以及在一些细节上的处理也是不同的。

Mantle的使用

简单的例子就不来了,可以直接到Github上面查看,这里上一个比较典型全面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (NSDictionary *)JSONDict {  if (_JSONDict == nil) {  _JSONDict = @{@"name" : [NSNull null],  "age" : @20,152)!important">"sex" : 0,152)!important">"login_date" : "1445136827",152)!important">"phone" : @{  "name" : "小明的iPhone",152)!important">"price" : 5000  }  "books" : @[  "西游记"},152)!important">"三国演义"}  ]  };  }  return _JSONDict; } 

对应模型的特点: 1、有NSNull对象,2、模型里面嵌套模型,3、模型里面有数组,数组里面有模型.

对应的模型如下:

18 19 20 21 22 23
.h typedef NS_ENUM(NSUInteger, Sex) {  SexMale,  SexFemale };  @interface BookForMantle : MTLModel <MTLJSONSerializing> property (nonatomic,210)!important">copy,210)!important">nullable) NSString *name; end  PhoneForMantle : assign) double price; UserForMantle : assign) NSInteger age; Sex sex; strong,210)!important">NSDate *loginDate; PhoneForMantle *phone; NSArray<BookForMantle *> *books; end 
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
m implementation PhoneForMantle  + (JSONKeyPathsByPropertyKey {  return "name",152)!important">"price"}; }  BookForMantle  + ("name"}; }  UserForMantle  // 该map不光是JSON->Model,Model->JSON也会用到 + ("age",152)!important">"sex",152)!important">"loginDate" : "login_date",152)!important">"phone",152)!important">"books"}; }  // 模型里面的模型 + (NSValueTransformer *)phoneTransformer {  return [MTLJSONAdapter dictionaryTransformerWithModelClass:[PhoneForMantle class]]; }  // 模型里面的数组 + (booksTransformer {  arrayTransformerWithModelClass:[BookForMantle // 时间 + (loginDateJSONTransformer {  MTLValueTransformer transformerUsingForwardBlock:^id(timeIntervalSince1970,210)!important">BOOL *success,210)!important">NSError *__autoreleasing *error) {  NSTimeInterval timeInterval = [timeIntervalSince1970 doubleValue];  date = [NSDate dateWithTimeIntervalSince1970:timeInterval];  date;  } reverseBlock:^date,210)!important">timeInterval = date.timeIntervalSince1970;  @(timeInterval).stringValue;  }]; }  end 

对应的解析方法:

10
// 注意: Mantle不会自动转类型,如:String->Int,一旦类型不匹配,直接crash // Json->Model // 该方法调用key-key map方法 self.userForMantle = [modelOfClass:[UserForMantle class] fromJSONDictionary:JSONDict error:nil]; // 这种方式只是简单的使用KVC进行赋值。不会调用key-key map方法,要求属性和JSON字典中的key名称相同,否则就crash // self.userForMantle = [UserForMantle modelWithDictionary:self.JSONDict error:&error];  // Model -> JSON // 一旦有属性为nil,Mantle会转换成NSNull对象放到JSON字典中,这里有一个坑,使用NSUserDefault存储这样的JSON字典时,程序crash,原因是不可以包含NSNull对象。 NSDictionary *jsonDict = [JSONDictionaryFromModel:userForMantle nil]; 

JSOMModel的使用

仍旧使用上面的例子,对应的模型如下:

53 54 55 56 57 58 59 60
// BookForJsonModel protocol BookForJsonModel BookForJsonModel : JSONModel BookForJSONModel // 前面是服务器字段,后面是模型属性字段 + (JSONKeyMapper *)keyMapper {  return [[JSONKeyMapper alloc] initWithDictionary:"name"  }]; } end  // PhoneForJSONModel PhoneForJSONModel : PhoneForJSONModel + ("price"  }]; } // UserForJSONModel UserForJSONModel : PhoneForJSONModel *phone; // 注意协议 BookForJSONModel> *UserForJSONModel + ("loginDate",152)!important">"books"  }]; }  // 允许所有字段为空 + (BOOL)propertyIsOptional:(NSString *)propertyName {  YES; } 对应的解析方法

4
// JSON->Model UserForJSONModel *user = [[UserForJSONModel initWithDictionary:// Model->JSON dict = [user toDictionary]; 

JSONModel各方面都挺好的,唯一需要注意的地方是它归档的方式,它不是将对象归档,而是转换成字典再归档。

16
-(instancetype)initWithCoder:(NSCoder *)decoder {  NSString* json = [decoder decodeObjectForKey:"json"];   JSONModelError *error = nil;  self = [self initWithString:json error:&error];  JMLog("%@",[error localizedDescription]);  }  self; }  -(void)encodeWithCoder:(encoder {  [encoder encodeObject:toJSONString forKey:"json"]; } 

MJExtension的使用

这个的使用方式就不介绍了,GitHub上写的非常详细。这里主要说说我在用的项目中时遇到问题及解决方式。情况大致是这样的: 最开始项目中的模型统统继承自BaseModel类,解析方式都是自己挨个手动解析。还自定义了一些譬如时间戳转自定义日期类型的方法。在换到MJExtension时,没法对我们的自定义解析方式进行兼容,全部重写肯定是不现实的,只能做兼容。最后通过阅读MJExtension的源码,找到了一个突破口。在BaseModel里面对MJExtension里面的一个方法使用Method Swizzling进行替换。大致代码如下:

3
BaseModel.h 里面添加一个接口,子类可以覆盖 /// json->模型,转换完成之后调用,以便进行自定义配置。 - (mc_keyValuesDidFinishConvertingToObjectWithData:(id)data; 
28 @H_571_1403@
m /* 替换方法: - (instancetype)setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context error:(NSError **)error; */ + (void)load {  static dispatch_once_t onceToken;  dispatch_once(&onceToken, ^{  Class class = [self class];   SEL originalSelector = selector(setKeyValues:context:error:);  swizzledSelector = mc_setKeyValues:Method originalMethod = class_getInstanceMethod(class,210)!important">originalSelector);  swizzledMethod = swizzledSelector);   BOOL success = class_addMethod(originalSelector,210)!important">method_getImplementation(swizzledMethod),210)!important">method_getTypeEncoding(swizzledMethod));  success) {  class_replaceMethod(swizzledSelector,210)!important">originalMethod),210)!important">originalMethod));  } else {  method_exchangeImplementations(originalMethod,210)!important">swizzledMethod);  }  }); }  - (mc_setKeyValues:(keyValues context:(NSManagedObjectContext *)context error:(NSError **)error {  MCDataModel *model = [mc_setKeyValues:context:if ([respondsToSelector:mc_keyValuesDidFinishConvertingToObjectWithData:)]) {  [mc_keyValuesDidFinishConvertingToObjectWithData:keyValues];  }  model; } 

这样在使用MJExtension将模型解析完成之后再调用mc_keyValuesDidFinishConvertingToObjectWithData: 将原始数据传递过去进行自定义配置。就可以很好的与老工程兼容了。


Mantle,MJExtension防崩溃比较:
对于这样一条数据:

{
"firstName": "张","lastName": "三","age": 23,"height": 172.3,"weight": 51.2,"sex": true
}

客户端属性声明为:
@property (nonatomic,copy) NSString *age;

Mantle的模型赋值:

if (![obj validateValue:&validatedValue forKey:key error:error]) return NO;
if (forceUpdate || value != validatedValue) 
{
[obj setValue:validatedValue forKey:key];
}
return YES;

它是使用了KVC的- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;方法来验证要赋的值的类型是否和key的类型是否匹配.该方法默认会调用a validator method whose name matches the pattern -validate<Key>:error:.因此我们还需要在模型里面写一个这样的方法:

-(BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError
{
    if ([*ioValue isKindOfClass:[NSNumber class]])
    {
        return YES;
    }
    return NO;
}

如果没写默认返回YES.Mantle貌似并没有帮我们做这么一步,所以如果你自己没写的话,那么上述验证的方法会返回YES.好的程序不应该总是期望服务器端永远都返回正确的东西,然而我们又无法知道服务器哪些字段会返回和我们不一致的类型.难道每一个属性都要写一个-validate<Key>:error:来判断?个人认为在这一点上Mantle做的很鸡肋.如果你没写-validate<Key>:error:而碰巧服务器端返回一个数字类型,那么你的程序很有可能会崩溃.

对于JSONModel的模型赋值:

//check for custom transformer
BOOL foundCustomTransformer = NO;
if ([valueTransformer respondsToSelector:selector]) {
    foundCustomTransformer = YES;
} else {
    //try for hidden custom transformer
    selectorName = [NSString stringWithFormat:@"__%@",selectorName];
    selector = NSSelectorFromString(selectorName);
    if ([valueTransformer respondsToSelector:selector]) {
        foundCustomTransformer = YES;
    }
}

//check if there's a transformer with that name
if (foundCustomTransformer) {

    //it's OK,believe me...
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    //transform the value
    jsonValue = [valueTransformer performSelector:selector withObject:jsonValue];
#pragma clang diagnostic pop

    if (![jsonValue isEqual:[self valueForKey:property.name]]) {
        [self setValue:jsonValue forKey: property.name];
    }

} else {

    // it's not a JSON data type,and there's no transformer for it
    // if property type is not supported - that's a programmer mistake -> exception
    @throw [NSException exceptionWithName:@"Type not allowed"
                                   reason:[@"%@ type not supported for %@.%@",property.type,[self class],property.name]
                                 userInfo:nil];
     可以看到它有做一个转换,对于一般的服务端数字,客户端NSString,或服务器端字符,客户端NSNumber,这样比较简单的转换,JSONModel已经帮我们实现好了,但是如果服务器端返回一个数组,但是客户端是NSString,那么这需要我们自己按照它的格式去写一个转换的方法,如果没写的话,JSONModel会抛出一个异常.然而通常我们需要服务器端传错了,我们客户端应该不崩溃,而是将对应的字段赋值为nil.

对于MJExtension的模型赋值:
由于MJ的考虑的情况比较全面,代码较多,有兴趣的可以自己去看,这里只截取最后一部分:

// value和property类型不匹配
if (propertyClass && ![value isKindOfClass:propertyClass])
{
value = nil;
}

可以看到如果类型不匹配那么对应的属性将被赋值为nil.而这些不需要我们写任何代码,可以的.最为厉害的就是当服务器传字符,客户端为NSUInteger类型时,Mantle,JSONModel都会崩溃,而MJ不会崩溃,且正确转换. 综上防崩溃最强的当属MJExtension,如果服务器端开发人员很菜的话强烈推荐使用MJExtension.

猜你在找的Json相关文章