用Mantle構(gòu)建Model層

在項(xiàng)目開(kāi)發(fā)過(guò)程中,經(jīng)常要自定義Model,然后在請(qǐng)求服務(wù)器得到數(shù)據(jù)后(一般是Json數(shù)據(jù)),用字典取值的方式給自定義的Model賦值,封裝成數(shù)據(jù)對(duì)象。這樣做有幾個(gè)問(wèn)題:

  • 服務(wù)器更新字段(或者添加字段)后,客戶端要在每個(gè)Model初始化的地方修改(或者添加)取值字段,過(guò)程繁瑣。
  • 實(shí)現(xiàn)這些自定義的Model對(duì)象序列化保存到本地,需要自己一個(gè)一個(gè)實(shí)現(xiàn),在字段添加或者修改的時(shí)候也要一個(gè)一個(gè)更改,過(guò)程繁瑣。
  • 自定義的Model沒(méi)辦法Copy,除非你實(shí)現(xiàn)<copying>協(xié)議,沒(méi)辦法反序列化成Json。

慶幸的是,偉大的github工程師們?cè)?code>OC平臺(tái)上提供了一個(gè)設(shè)計(jì)優(yōu)化、高度統(tǒng)一的框架Mantle來(lái)解決這些問(wèn)題。

Mantle為我們帶來(lái)的:

  • 實(shí)現(xiàn)了NSCopying protocol,子類終于可以直接copy了
  • 實(shí)現(xiàn)了NSCoding protocol,可以通過(guò)NSKeyedArchiver保存到本地了。(NSUserDefaults 的替換選擇)
  • 提供了-isEqual:和-hash的默認(rèn)實(shí)現(xiàn),model作NSDictionary的key方便了許多
  • 能在Model 和 Json 元數(shù)據(jù)之間相互轉(zhuǎn)換

Model的基本用法

自定義的Model

自定義的Model都需要集成自MTLModel,并且實(shí)現(xiàn)MTLJSONSerializing協(xié)議,例如下面的:

typedef enum : NSUInteger {
    GHIssueStateOpen,
    GHIssueStateClosed
} GHIssueState;

@interface GHIssue : MTLModel <MTLJSONSerializing>

@property (nonatomic, copy, readonly) NSURL *URL;
@property (nonatomic, copy, readonly) NSURL *HTMLURL;
@property (nonatomic, copy, readonly) NSNumber *number;
@property (nonatomic, assign, readonly) GHIssueState state;
@property (nonatomic, copy, readonly) NSString *reporterLogin;
@property (nonatomic, strong, readonly) GHUser *assignee;
@property (nonatomic, copy, readonly) NSDate *updatedAt;

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *body;

@property (nonatomic, copy, readonly) NSDate *retrievedAt;

@end

m 文件如下

@implementation GHIssue

+ (NSDateFormatter *)dateFormatter {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
    return dateFormatter;
}

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"URL": @"url",
        @"HTMLURL": @"html_url",
        @"reporterLogin": @"user.login",
        @"assignee": @"assignee",
        @"updatedAt": @"updated_at"
};
}

+ (NSValueTransformer *)URLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

+ (NSValueTransformer *)HTMLURLJSONTransformer {
    return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
}

+ (NSValueTransformer *)stateJSONTransformer {
    return [NSValueTransformer mtl_valueMappingTransformerWithDictionary:@{
        @"open": @(GHIssueStateOpen),
        @"closed": @(GHIssueStateClosed)
    }];
}

+ (NSValueTransformer *)assigneeJSONTransformer {
    return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:GHUser.class];
}

+ (NSValueTransformer *)updatedAtJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [self.dateFormatter dateFromString:str];
    } reverseBlock:^(NSDate *date) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    // Store a value that needs to be determined locally upon initialization.
    _retrievedAt = [NSDate date];

    return self;
}

@end

MTLJSONSerializing

繼承自MTLModel并實(shí)現(xiàn)了MTLJSONSerializing協(xié)議的對(duì)象可以這樣轉(zhuǎn)換

從字典數(shù)據(jù)(JSONDictionary表示字典元數(shù)據(jù))到 Model:

NSError *error = nil;
XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class    fromJSONDictionary:JSONDictionary error:&error]; 

從Model到JSONDictionary數(shù)據(jù)

NSDictionary *JSONDictionary = [MTLJSONAdapter JSONDictionaryFromModel:user];
+ (NSDictionary *)JSONKeyPathsByPropertyKey;用法如下:
@interface XYUser : MTLModel

@property (readonly, nonatomic, copy) NSString *name;
@property (readonly, nonatomic, strong) NSDate *createdAt;

@property (readonly, nonatomic, assign, getter = isMeUser) BOOL meUser;
@property (readonly, nonatomic, strong) XYHelper *helper;

@end

@implementation XYUser

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"createdAt": @"created_at",
        @"meUser": NSNull.null
    };
}

- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError **)error {
    self = [super initWithDictionary:dictionaryValue error:error];
    if (self == nil) return nil;

    _helper = [XYHelper helperWithName:self.name createdAt:self.createdAt];

    return self;
}

@end

返回的字典用來(lái)指定該Model的屬性從字典里面怎么取值,比如createdAt是從字典里面取created_at字段,指定@"meUser": NSNull.null表示不從字典里面取值,沒(méi)有在上面列出的Model屬性取和屬性同名的字典字段(比如name就從字典里面取name字段)。

+JSONTransformerForKey: 用法
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key {
    if ([key isEqualToString:@"createdAt"]) {
        return [NSValueTransformer valueTransformerForName:XYDateValueTransformerName];
    }

    return nil;
}

實(shí)現(xiàn)上面的方法,用來(lái)指定屬性從字典數(shù)據(jù)里面取出來(lái)的是什么類型的數(shù)據(jù)。比如上面createdAt屬性從字典里面取值后會(huì)自動(dòng)轉(zhuǎn)換為Date類型。

如果有很多類型需要指定取值類型,那豈不有很多if,這太不優(yōu)雅了!Mantle提供了更優(yōu)雅的方法:實(shí)現(xiàn)類似+<key>JSONTransformer的方法,來(lái)指定某個(gè)屬性從字典里面取值后的類型(或怎么取值):

+ (NSValueTransformer *)createdAtJSONTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
        return [self.dateFormatter dateFromString:str];
    } reverseBlock:^(NSDate *date) {
        return [self.dateFormatter stringFromDate:date];
    }];
}
+classForParsingJSONDictionary: 用法

當(dāng)你自定義了一組Model,實(shí)現(xiàn)+classForParsingJSONDictionary:方法可以指定在轉(zhuǎn)換deserializing字典的時(shí)候,用那個(gè)Model class

@interface XYMessage : MTLModel

@end

@interface XYTextMessage: XYMessage

@property (readonly, nonatomic, copy) NSString *body;

@end

@interface XYPictureMessage : XYMessage

@property (readonly, nonatomic, strong) NSURL *imageURL;

@end

@implementation XYMessage

+ (Class)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {
    if (JSONDictionary[@"image_url"] != nil) {
        return XYPictureMessage.class;
    }

    if (JSONDictionary[@"body"] != nil) {
        return XYTextMessage.class;
    }

    NSAssert(NO, @"No matching class for the JSON dictionary '%@'.",       JSONDictionary);
    return self;
}

@end

MTLJSONAdapter 會(huì)根據(jù)你傳入的字典數(shù)據(jù)實(shí)例化合適的類。

NSDictionary *textMessage = @{
    @"id": @1,
    @"body": @"Hello World!"
};

NSDictionary *pictureMessage = @{
    @"id": @2,
    @"image_url": @"http://example.com/lolcat.gif"
};

XYTextMessage *messageA = [MTLJSONAdapter modelOfClass:XYMessage.class  fromJSONDictionary:textMessage error:NULL];

XYPictureMessage *messageB = [MTLJSONAdapter modelOfClass:XYMessage.class   fromJSONDictionary:pictureMessage error:NULL];

Mantle代碼托管在:https://github.com/Mantle/Mantle

唱吧6.0版本使用Mantle后,據(jù)說(shuō):crash率比之前的版本有顯示的降低,并且Mantle相關(guān)的crash占總crash的比率不到3%,點(diǎn)這里查看更多關(guān)于唱吧使用Mantle后的總結(jié)
本文在:http://wangyangyang.gitcafe.com/2014/11/04/用Mantle構(gòu)建Model層/上面也有發(fā)表

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容