Question
通常我们用Objective-C写的模型层遇到了什么问题?
在开发过程中,我们常常会从服务端获取数据,数据通常是JSON格式。 比较常见的做法是把JSON数据转为Model对象,这样我们可以从Model对象的属性读取数据。
如:
1、我们每次都要通过-initWithDictionarty:(NSDictionary *)dict解析json数据,把值一个一个赋值给MODEL对象,经常还会出现[[obj objectForKey:@“KEY”] objectAtIndex:index] objectForKey:@“”]这种层次比较深的遍历,层次关系也要小心处理,比较容易出错。
或者即使多了一层封装:
_userAuthCode = authCode ? authCode : @"";
_userAuthToken = token ? token : @"";
_userUid = getStringInDict(jsonObject, @"uid");
_pauth = getStringInDict(jsonObject, @"pauth");
NSDictionary *info = getDictionaryInDict(jsonObject, @"info");
_userUsername = getStringInDict(info, @"username");
_userSex = getStringInDict(info, @"sex");
但是一样还要繁琐处理。
2、如果遇到服务端返回的类型不是确定的,更是蛋疼;有时候说是一个NSString,但是却给了个NSNumber,还有NSDate类型,给了NString类型,我们必须增加一些本来可以没有的判空。看到这么多if else就烦躁了。。
self.startKey = getStringInDict(result,@"startKey");
if (self.startKey == nil) {
if (getIntInDict(result, @"startKey") == 0) {
self.startKey = @"";
}else{
self.startKey = [NSString stringWithFormat:@"%d",getIntInDict(result,@"startKey")];
}
}
3、还有就是接口出了问题,返回了NSNull,假设没有做好处理,还有可以就Crash.
4、还有Model—>Json
5、序列化归档问题
What:
Mantle是一个用于简化Cocoa或Cocoa Touch程序中model层的第三方库。通常我们的应该中都会定义大量的model来表示各种数据结构,而这些model的初始化和编码解码都需要写大量的代码。
Mantle的优点在于能够大大地简化这些代码。是iOS和Mac平台下基于Objective-C编写的一个简单高效的模型层框架。
Mantle源码中最主要的内容包括:
- MTLModel类:通常是作为我们的Model的基类,该类提供了一些默认的行为来处理对象的初始化和归档操作,同时可以获取到对象所有属性的键值集合。
- MTLJSONAdapter类:用于在MTLModel对象和JSON字典之间进行相互转换,相当于是一个适配器。
- MTLJSONSerializing协议:需要与JSON字典进行相互转换的MTLModel的子类都需要实现该协议,以方便MTLJSONApadter对象进行转换。
##Use:
首先,看看Mantle的文件结构,文件还是蛮多的,不过常用的就那几个。MTDModel、MTLJSONAdapter
TDModel
要使用的Model都必须继承MTDModel
初始化
MTLModel默认的初始化方法-init并没有做什么事情,只是调用了下[super init]。而同时,它提供了一个另一个初始化方法:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
其中参数dictionaryValue是一个字典,它包含了用于初始化对象的key-value对。我们来看下它的具体实现:
- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
...
for (NSString *key in dictionary) {
// 1. 将value标记为__autoreleasing,这是因为在MTLValidateAndSetValue函数中,
// 可以会返回一个新的对象存在在该变量中
__autoreleasing id value = [dictionary objectForKey:key];
// 2. value如果为NSNull.null,会在使用前将其转换为nil
if ([value isEqual:NSNull.null]) value = nil;
// 3. MTLValidateAndSetValue函数利用KVC机制来验证value的值对于key是否有效,
// 如果无效,则使用使用默认值来设置key的值。
// 这里同样使用了对象的KVC特性来将value值赋值给model对应于key的属性。
// 有关MTLValidateAndSetValue的实现可参考源码,在此不做详细说明。
BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
if (!success) return nil;
}
...
}
子类可以重写该方法,可以在设置完对象的属性后做进一步的处理或初始化工作,记住:应该通过super来调用父类的实现**。
游戏盒的例子
h:
#import <Mantle/Mantle.h>
@interface MTDCommonGameListModel : MTLModel<MTLJSONSerializing>
@property (nonatomic,strong) NSString* gameId;
@property (nonatomic) int star;
@property (nonatomic) BOOL hasVideo;
@property (nonatomic) MTDGameDownloadUrlType downloadType;
@property (nonatomic,strong) NSDate* date;
@property (nonatomic,strong) NSString* imageUrl;
@property (nonatomic,strong) SubModel* subModel;
…
m:
#import "MTDCommonGameListModel.h"
@implementation MTDCommonGameListModel
+ (NSDictionary *)JSONKeyPathsByPropertyKey{
return @{
@"gameId":@"id",
@"star":@"star",
@"hasVideo":@"hasVideo",
@"downloadType":@"downloadType",
@"date":@"date”,
@"imageUrl":@"img",
@"subModel":@"subModel"
};
}
//服务端返回的类型兼容 gameId可支持服务端返回 字符串或数值类型
+ (NSValueTransformer *)gameIdJSONTransformer {
return [MTLValueTransformer transformerUsingForwardBlock:^id(id value, BOOL *success, NSError *__autoreleasing *error) {
return [value stringValue];
}];
}
//枚举类型映射
+ (NSValueTransformer *)stateJSONTransformer {
return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{ @"Others": @(MTDGameDownloadUrlTypeOthers), @"Itunes": @(MTDGameDownloadUrlTypeItunes), @"ShortLink": @(MTDGameDownloadUrlTypeShortLink)
}];
}
//date
+ (NSValueTransformer *)dateJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSNumber *dateNum) {
return [NSDate dateWithTimeIntervalSince1970:dateNum.floatValue];
}
reverseBlock:^(NSDate *date) {
return [NSString stringWithFormat:@"%f",[date timeIntervalSince1970]];
}];
}
//Model里面的Model实现
+ (NSValueTransformer *)subModelJSONTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:SubModel.class];
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error{
self = [super initWithDictionary:dictionaryValue error:error];
if (self == nil) {
return nil;
}
return self;
}
如果需要转换的属性可以增加对应的方法,通过运行时处理的,方法命名规则是 [属性JSONTransformer],对这个属性进行赋值时会调用这个方法先进行转换。
当JSON数据里有NSNull的类型时,我们不用做任何处理,会自动将该属性置为nil;
Model里面的Model实现
- (NSValueTransformer *)subModelJSONTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:subModel.class];
}
reverseBlock model —>JSON
reverseBlock是干什么的呢? 当要把Model转换回JSON数据时,如果设置了返回值,那么会将NSDate转回NSNumber返回JSON数据。
我们可以调用 MTLJSONAdapter的
+ (NSDictionary *)JSONDictionaryFromModel:<#(id<MTLJSONSerializing>)#> error:<#(NSError *__autoreleasing *)#>;
方法将一个Model实例转回JSON数据。
合并对象
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model;
该方法将当前对象指定的key属性的值与model参数对应的属性值按照指定的规则来进行合并,这种规则由我们自定义的-mergeFromModel:方法来确定。如果我们的子类中实现了-mergeFromModel:方法,则会调用它;如果没有找到,且model不为nil,则会用model的属性的值来替代当前对象的属性的值。具体实现如下:
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model {
NSParameterAssert(key != nil);
// 1. 根据传入的key拼接"mergeFromModel:"字符串,并从该字符串中获取到对应的selector
// 如果当前对象没有实现-mergeFromModel:方法,且model不为nil,则用model的属性值
// 替代当前对象的属性值
//
// MTLSelectorWithCapitalizedKeyPattern函数以C语言的方式来拼接方法字符串,具体实现请
// 参数源码,在此不详细说明
SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
if (![self respondsToSelector:selector]) {
if (model != nil) {
[self setValue:[model valueForKey:key] forKey:key];
}
return;
}
// 2. 通过NSInvocation方式来调用对应的-mergeFromModel:方法。
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: [self methodSignatureForSelector:selector]];
invocation.target = self;
invocation.selector = selector;
[invocation setArgument:&model atIndex:2];
[invocation invoke];
}
此外,MTLModel还提供了另一个方法来合并两个对象所有的属性值,即:
- (void)mergeValuesForKeysFromModel:(MTLModel *)model;
需要注意的是model必须是当前对象所属类或其子类。
<NSCoding>, <NSCopying>, -isEqual:和-hash
在你的子类里面生命属性,MTLModel可以提供这些方法的默认实现
Mantle配合归档
MTLModel默认实现了 NSCoding协议,可以利用NSKeyedArchiver方便的对对象进行归档和解档。
Mantle配合Core Data
….这个后续有时间再补充,目前还没整理
##****参考:
https://github.com/mantle/mantle
https://segmentfault.com/a/1190000003882034
http://www.jianshu.com/p/937266eb6635