亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Mantle--國外程序員最常用的iOS模型&字典轉換框架

2019-11-14 18:06:37
字體:
來源:轉載
供稿:網友

Mantle簡介

Mantle是iOS和Mac平臺下基于Objective-C編寫的一個簡單高效的模型層框架。

Mantle能做什么

Mantle可以輕松把JSON數據、字典(Dictionary)和模型(即Objective對象)之間的相互轉換,支持自定義映射,并且內置實現了NSCoding和NSCoping,大大簡化歸檔操作。

為什么要使用Mantle

傳統的模型層方案遇到的問題

通常我們用Objective-C寫的模型層遇到了什么問題?

我們可以用Github API來舉例?,F在假設我們想用Objective-C展現一個Github Issue,應該怎么做?

目前我們可以想到

  1. 直接解析JSON數據字典,然后展現給UI

  2. 將JSON數據轉換為模型,在賦值給UI

關于1,弊端有很多,可以參考我的這篇文章:在iOS開發中使用字典轉模型,現在假設我們選擇了2,我們大致會定義下面的GHIssue模型:

GHIssue.h

	#import <Foundation/Foundation.h>	typedef enum : NSUInteger {	    GHIssueStateOpen,	    GHIssueStateClosed	} GHIssueState;		@class GHUser;	@interface GHIssue : NSObject <NSCoding, NSCopying>		@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, copy, readonly) NSDate *updatedAt;	@property (nonatomic, strong, readonly) GHUser *assignee;	@property (nonatomic, copy, readonly) NSDate *retrievedAt;		@property (nonatomic, copy) NSString *title;	@property (nonatomic, copy) NSString *body;		- (instancetype)initWithDictionary:(NSDictionary *)dictionary;		@end

GHIssue.m

	#import "GHIssue.h"	#import "GHUser.h"		@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;	}		- (instancetype)initWithDictionary:(NSDictionary *)dictionary {	    self = [self init];	    if (self == nil) return nil;	    	    _URL = [NSURL URLWithString:dictionary[@"url"]];	    _HTMLURL = [NSURL URLWithString:dictionary[@"html_url"]];	    _number = dictionary[@"number"];	    	    if ([dictionary[@"state"] isEqualToString:@"open"]) {	        _state = GHIssueStateOpen;	    } else if ([dictionary[@"state"] isEqualToString:@"closed"]) {	        _state = GHIssueStateClosed;	    }	    	    _title = [dictionary[@"title"] copy];	    _retrievedAt = [NSDate date];	    _body = [dictionary[@"body"] copy];	    _reporterLogin = [dictionary[@"user"][@"login"] copy];	    _assignee = [[GHUser alloc] initWithDictionary:dictionary[@"assignee"]];	    	    _updatedAt = [self.class.dateFormatter dateFromString:dictionary[@"updated_at"]];	    	    return self;	}		- (instancetype)initWithCoder:(NSCoder *)coder {	    self = [self init];	    if (self == nil) return nil;	    	    _URL = [coder decodeObjectForKey:@"URL"];	    _HTMLURL = [coder decodeObjectForKey:@"HTMLURL"];	    _number = [coder decodeObjectForKey:@"number"];	    _state = [coder decodeIntegerForKey:@"state"];	    _title = [coder decodeObjectForKey:@"title"];	    _retrievedAt = [NSDate date];	    _body = [coder decodeObjectForKey:@"body"];	    _reporterLogin = [coder decodeObjectForKey:@"reporterLogin"];	    _assignee = [coder decodeObjectForKey:@"assignee"];	    _updatedAt = [coder decodeObjectForKey:@"updatedAt"];	    	    return self;	}		- (void)encodeWithCoder:(NSCoder *)coder {	    if (self.URL != nil) [coder encodeObject:self.URL forKey:@"URL"];	    if (self.HTMLURL != nil) [coder encodeObject:self.HTMLURL forKey:@"HTMLURL"];	    if (self.number != nil) [coder encodeObject:self.number forKey:@"number"];	    if (self.title != nil) [coder encodeObject:self.title forKey:@"title"];	    if (self.body != nil) [coder encodeObject:self.body forKey:@"body"];	    if (self.reporterLogin != nil) [coder encodeObject:self.reporterLogin forKey:@"reporterLogin"];	    if (self.assignee != nil) [coder encodeObject:self.assignee forKey:@"assignee"];	    if (self.updatedAt != nil) [coder encodeObject:self.updatedAt forKey:@"updatedAt"];	    	    [coder encodeInteger:self.state forKey:@"state"];	}		- (instancetype)copyWithZone:(NSZone *)zone {	    GHIssue *issue = [[self.class allocWithZone:zone] init];	    issue->_URL = self.URL;	    issue->_HTMLURL = self.HTMLURL;	    issue->_number = self.number;	    issue->_state = self.state;	    issue->_reporterLogin = self.reporterLogin;	    issue->_assignee = self.assignee;	    issue->_updatedAt = self.updatedAt;	    	    issue.title = self.title;	    issue->_retrievedAt = [NSDate date];	    issue.body = self.body;	    	    return issue;	}		- (NSUInteger)hash {	    return self.number.hash;	}		- (BOOL)isEqual:(GHIssue *)issue {	    if (![issue isKindOfClass:GHIssue.class]) return NO;	    	    return [self.number isEqual:issue.number] && [self.title isEqual:issue.title] && [self.body isEqual:issue.body];	}

GHUser.h

	@interface GHUser : NSObject <NSCoding, NSCopying>	@property (nonatomic, copy) NSString *login;	@property (nonatomic, assign) NSUInteger id;	@property (nonatomic, copy) NSString *avatarUrl;	@property (nonatomic, copy) NSString *gravatarId;	@property (nonatomic, copy) NSString *url;	@property (nonatomic, copy) NSString *htmlUrl;	@property (nonatomic, copy) NSString *followersUrl;	@property (nonatomic, copy) NSString *followingUrl;	@property (nonatomic, copy) NSString *gistsUrl;	@property (nonatomic, copy) NSString *starredUrl;	@property (nonatomic, copy) NSString *subscriptionsUrl;	@property (nonatomic, copy) NSString *organizationsUrl;	@property (nonatomic, copy) NSString *reposUrl;	@property (nonatomic, copy) NSString *eventsUrl;	@property (nonatomic, copy) NSString *receivedEventsUrl;	@property (nonatomic, copy) NSString *type;	@property (nonatomic, assign) BOOL siteAdmin;		- (id)initWithDictionary:(NSDictionary *)dictionary;		@end

你會看到,如此簡單的事情卻有很多弊端。甚至,還有一些其他問題,這個例子里面沒有展示出來。

  1. 無法使用服務器的新數據來更新這個 GHIssue
  2. 無法反過來將 GHIssue 轉換成 JSON
  3. 對于GHIssueState,如果枚舉改編了,現有的歸檔會崩潰
  4. 如果 GHIssue 接口改變了,現有的歸檔會崩潰。

使用MTLModel

如果使用MTLModel,我們可以這樣,聲明一個類繼承自MTLModel

	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	@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",	        @"number": @"number",	        @"state": @"state",	        @"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 [MTLJSONAdapter dictionaryTransformerWithModelClass:GHUser.class];	}		+ (NSValueTransformer *)updatedAtJSONTransformer {	    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, NSError *__autoreleasing *error) {	        return [self.dateFormatter dateFromString:dateString];	    } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {	        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

很明顯,我們不需要再去實現<NSCoding>, <NSCopying>, -isEqual:-hash。在你的子類里面生命屬性,MTLModel可以提供這些方法的默認實現。

最初例子里面的問題,在這里都得到了很好的解決。

  • MTLModel提供了一個- (void)mergeValueForKey:(NSString *)key fromModel:(id<MTLModel>)model{},可以與其他任何實現了MTLModel協議的模型對象集成。

  • +[MTLJSONAdapter JSONDictionaryFromModel:error:]可以把任何遵循MTLJSONSerializing>``協議的對象轉換成JSON字典,+[MTLJSONAdapter JSONArrayFromModels:error:]```類似,不過轉換的是一個數組。

MTLJSONAdapter中的fromJSONDictionaryJSONDictionaryFromModel可以實現模型和JSON的相互轉化。

JSONKeyPathsByPropertyKey可以實現模型和JSON的自定義映射。

JSONTransformerForKey可以對JSON和模型不同類型進行映射。

classForParsingJSONDictionary 如果你使用了類簇(關于類簇,請參考:類簇在iOS開發中的應用),classForParsingJSONDictionary可以讓你選擇使用哪一個類進行JSON反序列化。

  • MTLModel可以用歸檔很好的存儲模型而不需要去實現令人厭煩的NSCoding協議。 -decodeValueForKey:withCoder:modelVersion:方法在解碼時會自動調用,如果重寫,可以方便的進行自定義。

持久化

Mantle配合歸檔

MTLModel默認實現了 NSCoding協議,可以利用NSKeyedArchiver方便的對對象進行歸檔和解檔。

Mantle配合Core Data

除了SQLite、FMDB之外,如果你想在你的數據里面執行復雜的查詢,處理很多關系,支持撤銷恢復,Core Data非常適合。

然而,這樣也帶來了一些痛點:

  • 仍然有很多弊端Managed objects解決了上面看到的一些弊端,但是Core Data自生也有他的弊端。正確的配置Core Data和獲取數據需要很多行代碼。
  • 很難保持正確性。甚至有經驗的人在使用Core Data時也會犯錯,并且這些問題框架是無法解決的。

如果你想獲取JSON對象,Core Data需要做很多工作,但是卻只能得到很少的回報。

但是,如果你已經在你的APP里面使用了Core Data,Mantle將仍然會是你的API和你的managed model objects之間一個很方便的轉換層。

Mantle配合MagicRecord(一個Core Data框架)

參考 MagicalRecord配合Mantle

Mantle為我們帶來的好處

  • 實現了NSCopying protocol,子類可以直接copy是多么爽的事情

  • 實現了NSCoding protocol,跟NSUserDefaults說拜拜

  • 提供了-isEqual:和-hash的默認實現,model作NSDictionary的key方便了許多

  • 支持自定義映射,這在接口改變的情況下很有用

  • 簡單且把一件事情做好,不摻雜網絡相關的操作

合理選擇

雖然上面說了一系列的好處,但如果你的App的代碼規模只有幾萬行,或者API只有十幾個,或者沒有遇到上面這些問題, 建議還是不要引入了,殺雞用指甲刀就夠了。但是,Mantle的實現和思路是值得每位iOS工程師學習和借鑒的。

代碼

https://github.com/terwer/MantleDemo

參考

https://github.com/mantle/mantle

http://segmentfault.com/a/1190000002431365

http://yyny.me/ios/Mantle%E3%80%81JSONModel%E3%80%81MJExtension%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/

PS: 本文由我們iOS122的小伙伴@TerwerGreen整理編輯,歡迎大家到他的個人博客terwer共同論道!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91深夜福利视频| 成人午夜高潮视频| 亚洲视频综合网| 久久国产视频网站| 国产精品27p| 久久亚洲精品网站| 日韩精品免费观看| 亚洲女人天堂成人av在线| 国产精品一区二区在线| 亚洲国产91色在线| 色yeye香蕉凹凸一区二区av| 久久久久一本一区二区青青蜜月| 久热精品视频在线观看| 亚洲欧美在线第一页| 国产欧美日韩亚洲精品| 日韩在线免费观看视频| 97国产成人精品视频| 国产一区二区三区在线观看网站| 欧美极度另类性三渗透| 欧美亚洲一级片| 久久天天躁狠狠躁夜夜av| 日韩成人av在线播放| 欧美视频在线观看 亚洲欧| 欧美超级免费视 在线| 成人免费视频网址| 亚洲欧美另类国产| 国产精品国产自产拍高清av水多| 国产成人aa精品一区在线播放| 国产日产欧美精品| 国产亚洲成av人片在线观看桃| 91麻豆国产语对白在线观看| 亚洲国产高清自拍| 国产精品黄色av| 亚洲精品久久久久中文字幕欢迎你| 亚洲欧美日韩直播| 国产成人极品视频| 日韩成人高清在线| 亚洲xxx视频| 欧美寡妇偷汉性猛交| 亚洲精品按摩视频| 在线免费观看羞羞视频一区二区| 欧美日韩亚洲91| 亚洲福利视频专区| 国产精品88a∨| 38少妇精品导航| 欧美高清videos高潮hd| 久久久精品久久久久| 久久精品国产视频| 欧美日韩精品在线| 欧美激情免费视频| 日韩欧美精品网站| 裸体女人亚洲精品一区| 日韩国产高清污视频在线观看| 午夜精品美女自拍福到在线| 在线观看免费高清视频97| 国产精品 欧美在线| 欧美老少配视频| 日韩在线精品视频| 日本国产高清不卡| 亚洲人成网站999久久久综合| 亚洲国产精久久久久久| 亚洲高清免费观看高清完整版| 在线观看中文字幕亚洲| 国产一区二区美女视频| 国产欧美在线观看| 91亚洲一区精品| 欧美性色19p| 欧美性感美女h网站在线观看免费| 日韩av在线影院| 中文国产成人精品| 日韩精品一区二区视频| 91精品国产乱码久久久久久蜜臀| 国产精品一区二区久久| 欧美人与性动交| 日韩电影免费观看在线观看| 亚洲精品久久久久久下一站| 欧美日韩国产综合视频在线观看中文| 久久久久一本一区二区青青蜜月| 久久天堂电影网| 91香蕉嫩草神马影院在线观看| 日韩av一区在线观看| 国产一区二区三区丝袜| 欧美色道久久88综合亚洲精品| 欧美黄色片免费观看| 欧美日韩高清在线观看| 欧美一级bbbbb性bbbb喷潮片| 欧美性资源免费| 欧美日韩国产色| 中文字幕亚洲第一| 久久青草福利网站| 国产欧美在线观看| 在线精品播放av| 最新日韩中文字幕| 欧美亚洲成人精品| 欧美电影在线播放| 亚洲精品在线观看www| 成人网在线免费观看| 96sao精品视频在线观看| 中文字幕精品在线视频| 亚洲mm色国产网站| 热久久这里只有精品| 97热精品视频官网| 欧美激情综合亚洲一二区| 97在线视频免费看| 国产91色在线| 一区二区三区视频观看| 成人黄色大片在线免费观看| 日韩av影片在线观看| 欧美精品一区二区免费| 中文字幕视频在线免费欧美日韩综合在线看| 国产精品视频一区国模私拍| 97视频在线观看网址| 国内精品久久久久影院优| 综合欧美国产视频二区| 欧美在线视频在线播放完整版免费观看| 国内精品久久影院| 欧美一级淫片videoshd| 国产精品盗摄久久久| 成人av在线亚洲| 欧美肥老太性生活视频| 中文字幕日韩免费视频| 538国产精品一区二区免费视频| 久久国产加勒比精品无码| 国产成人在线亚洲欧美| 亚洲人成网站色ww在线| 久久视频这里只有精品| 国产亚洲欧美日韩精品| 亚洲国产成人精品久久| 久久久久久国产精品美女| 国产欧美一区二区三区久久| 欧美大片在线看| 欧美日韩在线影院| 日韩免费av在线| 欧美亚洲成人网| 亚洲欧美国产一区二区三区| 亚洲小视频在线| 久久久久久亚洲精品不卡| 欧美精品福利视频| 精品网站999www| 自拍偷拍亚洲区| 国产精品观看在线亚洲人成网| 91丝袜美腿美女视频网站| 黄色成人av在线| 色噜噜狠狠色综合网图区| 欧美激情视频三区| 国产精品视频白浆免费视频| 久久久人成影片一区二区三区观看| …久久精品99久久香蕉国产| 日韩欧美有码在线| 日韩在线视频免费观看| 日韩电影免费在线观看中文字幕| 性日韩欧美在线视频| 亚洲欧美日韩在线高清直播| 亚洲欧美日韩中文在线制服| 国语自产精品视频在线看一大j8| 亚洲iv一区二区三区| 日本精品久久久久久久| 欧美日韩在线观看视频| 国产精品久久久久久亚洲影视| 亚洲国产黄色片| 亚洲第一级黄色片| 国产欧美一区二区三区四区| 日产精品久久久一区二区福利| 日韩视频免费观看|