日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Mantle--国外程序员最常用的iOS模型字典转换框架

發布時間:2023/12/18 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mantle--国外程序员最常用的iOS模型字典转换框架 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Mantle簡介

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

Mantle能做什么

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

為什么要使用Mantle

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

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

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

目前我們可以想到

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

  • 將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

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

  • 無法使用服務器的新數據來更新這個 GHIssue
  • 無法反過來將 GHIssue 轉換成 JSON
  • 對于GHIssueState,如果枚舉改編了,現有的歸檔會崩潰
  • 如果 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中的fromJSONDictionary和JSONDictionaryFromModel可以實現模型和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共同論道!

    轉載于:https://www.cnblogs.com/ios122/p/4895208.html

    總結

    以上是生活随笔為你收集整理的Mantle--国外程序员最常用的iOS模型字典转换框架的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。