Question
通常我們用Objective-C寫的模型層遇到了什么問題蝇裤?
在開發(fā)過程中月劈,我們常常會從服務(wù)端獲取數(shù)據(jù)台舱,數(shù)據(jù)通常是JSON格式。 比較常見的做法是把JSON數(shù)據(jù)轉(zhuǎn)為Model對象付呕,這樣我們可以從Model對象的屬性讀取數(shù)據(jù)。
如:
1廓八、我們每次都要通過-initWithDictionarty:(NSDictionary *)dict解析json數(shù)據(jù)锅很,把值一個一個賦值給MODEL對象,經(jīng)常還會出現(xiàn)[[obj objectForKey:@“KEY”] objectAtIndex:index] objectForKey:@“”]這種層次比較深的遍歷多望,層次關(guān)系也要小心處理嫩舟,比較容易出錯。
或者即使多了一層封裝:
_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家厌、如果遇到服務(wù)端返回的類型不是確定的,更是蛋疼椎工;有時(shí)候說是一個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燎猛,假設(shè)沒有做好處理,還有可以就Crash.
4照皆、還有Model—>Json
5重绷、序列化歸檔問題
What:
Mantle是一個用于簡化Cocoa或Cocoa Touch程序中model層的第三方庫。通常我們的應(yīng)該中都會定義大量的model來表示各種數(shù)據(jù)結(jié)構(gòu)膜毁,而這些model的初始化和編碼解碼都需要寫大量的代碼昭卓。
Mantle的優(yōu)點(diǎn)在于能夠大大地簡化這些代碼。是iOS和Mac平臺下基于Objective-C編寫的一個簡單高效的模型層框架瘟滨。
??Mantle源碼中最主要的內(nèi)容包括:
- MTLModel類:通常是作為我們的Model的基類候醒,該類提供了一些默認(rèn)的行為來處理對象的初始化和歸檔操作,同時(shí)可以獲取到對象所有屬性的鍵值集合杂瘸。
- MTLJSONAdapter類:用于在MTLModel對象和JSON字典之間進(jìn)行相互轉(zhuǎn)換倒淫,相當(dāng)于是一個適配器。
- MTLJSONSerializing協(xié)議:需要與JSON字典進(jìn)行相互轉(zhuǎn)換的MTLModel的子類都需要實(shí)現(xiàn)該協(xié)議败玉,以方便MTLJSONApadter對象進(jìn)行轉(zhuǎn)換敌土。
##Use:
首先,看看Mantle的文件結(jié)構(gòu)运翼,文件還是蠻多的返干,不過常用的就那幾個。MTDModel南蹂、MTLJSONAdapter
TDModel
要使用的Model都必須繼承MTDModel
初始化
MTLModel默認(rèn)的初始化方法-init并沒有做什么事情犬金,只是調(diào)用了下[super init]。而同時(shí)六剥,它提供了一個另一個初始化方法:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error;
其中參數(shù)dictionaryValue是一個字典晚顷,它包含了用于初始化對象的key-value對。我們來看下它的具體實(shí)現(xiàn):
- (instancetype)initWithDictionary:(NSDictionary *)dictionary error:(NSError **)error {
...
for (NSString *key in dictionary) {
// 1. 將value標(biāo)記為__autoreleasing疗疟,這是因?yàn)樵贛TLValidateAndSetValue函數(shù)中该默,
// 可以會返回一個新的對象存在在該變量中
__autoreleasing id value = [dictionary objectForKey:key];
// 2. value如果為NSNull.null,會在使用前將其轉(zhuǎn)換為nil
if ([value isEqual:NSNull.null]) value = nil;
// 3. MTLValidateAndSetValue函數(shù)利用KVC機(jī)制來驗(yàn)證value的值對于key是否有效策彤,
// 如果無效栓袖,則使用使用默認(rèn)值來設(shè)置key的值。
// 這里同樣使用了對象的KVC特性來將value值賦值給model對應(yīng)于key的屬性店诗。
// 有關(guān)MTLValidateAndSetValue的實(shí)現(xiàn)可參考源碼裹刮,在此不做詳細(xì)說明。
BOOL success = MTLValidateAndSetValue(self, key, value, YES, error);
if (!success) return nil;
}
...
}
子類可以重寫該方法庞瘸,可以在設(shè)置完對象的屬性后做進(jìn)一步的處理或初始化工作捧弃,記住:應(yīng)該通過super來調(diào)用父類的實(shí)現(xiàn)**。
游戲盒的例子
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"
};
}
//服務(wù)端返回的類型兼容 gameId可支持服務(wù)端返回 字符串或數(shù)值類型
+ (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實(shí)現(xiàn)
+ (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;
}
如果需要轉(zhuǎn)換的屬性可以增加對應(yīng)的方法违霞,通過運(yùn)行時(shí)處理的嘴办,方法命名規(guī)則是 [屬性JSONTransformer],對這個屬性進(jìn)行賦值時(shí)會調(diào)用這個方法先進(jìn)行轉(zhuǎn)換。
當(dāng)JSON數(shù)據(jù)里有NSNull的類型時(shí)买鸽,我們不用做任何處理涧郊,會自動將該屬性置為nil;
Model里面的Model實(shí)現(xiàn)
- (NSValueTransformer *)subModelJSONTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:subModel.class];
}
reverseBlock model —>JSON
reverseBlock是干什么的呢眼五? 當(dāng)要把Model轉(zhuǎn)換回JSON數(shù)據(jù)時(shí)妆艘,如果設(shè)置了返回值,那么會將NSDate轉(zhuǎn)回NSNumber返回JSON數(shù)據(jù)弹砚。
我們可以調(diào)用 MTLJSONAdapter的
+ (NSDictionary *)JSONDictionaryFromModel:<#(id<MTLJSONSerializing>)#> error:<#(NSError *__autoreleasing *)#>;
方法將一個Model實(shí)例轉(zhuǎn)回JSON數(shù)據(jù)双仍。
合并對象
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model;
該方法將當(dāng)前對象指定的key屬性的值與model參數(shù)對應(yīng)的屬性值按照指定的規(guī)則來進(jìn)行合并,這種規(guī)則由我們自定義的-mergeFromModel:方法來確定桌吃。如果我們的子類中實(shí)現(xiàn)了-mergeFromModel:方法,則會調(diào)用它苞轿;如果沒有找到茅诱,且model不為nil,則會用model的屬性的值來替代當(dāng)前對象的屬性的值搬卒。具體實(shí)現(xiàn)如下:
- (void)mergeValueForKey:(NSString *)key fromModel:(MTLModel *)model {
NSParameterAssert(key != nil);
// 1. 根據(jù)傳入的key拼接"mergeFromModel:"字符串瑟俭,并從該字符串中獲取到對應(yīng)的selector
// 如果當(dāng)前對象沒有實(shí)現(xiàn)-mergeFromModel:方法,且model不為nil契邀,則用model的屬性值
// 替代當(dāng)前對象的屬性值
//
// MTLSelectorWithCapitalizedKeyPattern函數(shù)以C語言的方式來拼接方法字符串摆寄,具體實(shí)現(xiàn)請
// 參數(shù)源碼,在此不詳細(xì)說明
SEL selector = MTLSelectorWithCapitalizedKeyPattern("merge", key, "FromModel:");
if (![self respondsToSelector:selector]) {
if (model != nil) {
[self setValue:[model valueForKey:key] forKey:key];
}
return;
}
// 2. 通過NSInvocation方式來調(diào)用對應(yīng)的-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必須是當(dāng)前對象所屬類或其子類古戴。
<NSCoding>, <NSCopying>, -isEqual:和-hash
在你的子類里面生命屬性欠橘,MTLModel可以提供這些方法的默認(rèn)實(shí)現(xiàn)
Mantle配合歸檔
MTLModel默認(rèn)實(shí)現(xiàn)了 NSCoding協(xié)議,可以利用NSKeyedArchiver方便的對對象進(jìn)行歸檔和解檔现恼。
Mantle配合Core Data
….這個后續(xù)有時(shí)間再補(bǔ)充肃续,目前還沒整理
##****參考:
https://github.com/mantle/mantle
https://segmentfault.com/a/1190000003882034
http://www.reibang.com/p/937266eb6635