在項目開發過程中,經常要自定義Model,然后在請求服務器得到數據后(一般是Json數據),用字典取值的方式給自定義的Model賦值,封裝成數據對象。這樣做有幾個問題:
- 服務器更新字段(或者添加字段)后,客戶端要在每個Model初始化的地方修改(或者添加)取值字段,過程繁瑣。
- 實現這些自定義的Model對象序列化保存到本地,需要自己一個一個實現,在字段添加或者修改的時候也要一個一個更改,過程繁瑣。
- 自定義的Model沒辦法Copy,除非你實現<copying>協議,沒辦法反序列化成Json。
慶幸的是,偉大的github
工程師們在OC
平臺上提供了一個設計優化、高度統一的框架Mantle
來解決這些問題。
Mantle為我們帶來的:
- 實現了
NSCopying protocol
,子類終于可以直接copy了 - 實現了
NSCoding protocol
,可以通過NSKeyedArchiver
保存到本地了。(NSUserDefaults
的替換選擇) - 提供了
-isEqual
:和-hash
的默認實現,model作NSDictionary的key方便了許多 - 能在Model 和 Json 元數據之間相互轉換
Model的基本用法
自定義的Model
自定義的Model都需要集成自MTLModel
,并且實現MTLJSONSerializing
協議,例如下面的:
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
并實現了MTLJSONSerializing
協議的對象可以這樣轉換
從字典數據(JSONDictionary表示字典元數據)到 Model:
NSError *error = nil;
XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class fromJSONDictionary:JSONDictionary error:&error];
從Model到JSONDictionary數據
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
返回的字典用來指定該Model的屬性從字典里面怎么取值,比如createdAt
是從字典里面取created_at
字段,指定@"meUser": NSNull.null
表示不從字典里面取值,沒有在上面列出的Model屬性取和屬性同名的字典字段(比如name
就從字典里面取name
字段)。
+JSONTransformerForKey: 用法
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key {
if ([key isEqualToString:@"createdAt"]) {
return [NSValueTransformer valueTransformerForName:XYDateValueTransformerName];
}
return nil;
}
實現上面的方法,用來指定屬性從字典數據里面取出來的是什么類型的數據。比如上面createdAt
屬性從字典里面取值后會自動轉換為Date
類型。
如果有很多類型需要指定取值類型,那豈不有很多if,這太不優雅了!Mantle
提供了更優雅的方法:實現類似+<key>JSONTransformer
的方法,來指定某個屬性從字典里面取值后的類型(或怎么取值):
+ (NSValueTransformer *)createdAtJSONTransformer {
return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) {
return [self.dateFormatter dateFromString:str];
} reverseBlock:^(NSDate *date) {
return [self.dateFormatter stringFromDate:date];
}];
}
+classForParsingJSONDictionary: 用法
當你自定義了一組Model,實現+classForParsingJSONDictionary:
方法可以指定在轉換deserializing
字典的時候,用那個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
會根據你傳入的字典數據實例化合適的類。
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
后,據說:crash率比之前的版本有顯示的降低,并且Mantle相關的crash占總crash的比率不到3%
,點這里查看更多關于唱吧使用Mantle
后的總結
本文在:http://wangyangyang.gitcafe.com/2014/11/04/用Mantle構建Model層/上面也有發表