Mantle을 사용하면 Cocoa 또는 Cocoa Touch 애플리케이션을 위한 간단한 모델 레이어를 쉽게 작성할 수 있습니다.
일반적으로 Objective-C에서 모델 객체를 작성하는 방식에 어떤 문제가 있나요?
데모를 위해 GitHub API를 사용해 보겠습니다. Objective-C에서 GitHub 문제를 일반적으로 어떻게 표현합니까?
typedef 열거형 : NSUInteger { GHIssueStateOpen, GHIssueStateClosed } GHIssueState;@interface GHIssue : NSObject <NSCoding, NSCopying>@property(비원자, 복사, 읽기 전용) NSURL *URL;@property(비원자, 복사, 읽기 전용) NSURL *HTMLURL;@property(비원자, 복사, 읽기 전용) NSNumber * 숫자;@속성(비원자적, 할당, 읽기 전용) GHIssueState 상태;@속성 (비원자, 복사, 읽기 전용) NSString *reporterLogin;@property (비원자, 복사, 읽기 전용) NSDate *updatedAt;@property (비원자, 강력, 읽기 전용) GHUser *signee;@property (비원자, 복사, 읽기 전용) NSDate *retrievedAt; @property(비원자, 복사본) NSString *title;@property(비원자, 복사본) NSString *몸; - (id)initWithDictionary:(NSDictionary *)dictionary;@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; } - (id)initWithDictionary:(NSDictionary *)dictionary { self = [self init];if (self == nil) return nil; _URL = [NSURL URLWithString:dictionary[@"url"]]; _HTMLURL = [NSURL URLWithString:dictionary[@"html_url"]]; _number = 사전[@"number"];if ([dictionary[@"state"] isEqualToString:@"open"]) { _state = GHIssueStateOpen; } else if ([dictionary[@"state"] isEqualToString:@"closed"]) { _state = GHIssueStateClosed; } _title = [사전[@"title"] 사본]; _retrievedAt = [NSDate 날짜]; _body = [사전[@"body"] 복사]; _reporterLogin = [사전[@"user"][@"login"] 복사]; _할당자 = [[GHUser 할당] initWithDictionary:dictionary[@"할당자"]]; _updatedAt = [self.class.dateFormatter dateFromString:dictionary[@"updated_at"]];return self; } - (id)initWithCoder:(NSCoder *)coder { self = [self init];if (self == nil) return nil; _URL = [코더 decodeObjectForKey:@"URL"]; _HTMLURL = [코더 decodeObjectForKey:@"HTMLURL"]; _number = [코더 decodeObjectForKey:@"number"]; _state = [coder decodeUnsignedIntegerForKey:@"state"]; _title = [코더 decodeObjectForKey:@"title"]; _retrievedAt = [NSDate 날짜]; _body = [코더 decodeObjectForKey:@"body"]; _reporterLogin = [코더 decodeObjectForKey:@"reporterLogin"]; _할당자 = [코더 decodeObjectForKey:@"할당자"]; _updatedAt = [코더 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.signee != nil) [coder encodeObject:self.assetee forKey:@"할당자"];if (self.updatedAt != nil) [coder encodeObject:self.updatedAt forKey:@"updatedAt"]; [coder encodeUnsignedInteger:self.state forKey:@"state"]; } - (id)copyWithZone:(NSZone *)zone { GHIssue *issue = [[self.class allocWithZone:zone] init]; 이슈->_URL = self.URL; 문제->_HTMLURL = self.HTMLURL; 이슈->_번호 = self.number; 이슈->_state = self.state; 이슈->_reporterLogin = self.reporterLogin; 이슈->_할당자 = self.할당자; 이슈->_updatedAt = self.updatedAt; 이슈.제목 = 자체.제목; issue->_retrievedAt = [NSDate 날짜]; issue.body = self.body;return 이슈; } - (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]; }@끝
휴, 너무 간단한 것에 대한 상용구가 너무 많군요! 그리고 그럼에도 불구하고 이 예제에서 다루지 않는 몇 가지 문제가 있습니다.
서버의 새 데이터로 GHIssue
업데이트할 방법이 없습니다.
GHIssue
다시 JSON으로 변환할 수 있는 방법은 없습니다.
GHIssueState
있는 그대로 인코딩되어서는 안 됩니다. 나중에 열거형이 변경되면 기존 아카이브가 손상될 수 있습니다.
향후 GHIssue
의 인터페이스가 변경되면 기존 아카이브가 손상될 수 있습니다.
Core Data는 특정 문제를 매우 잘 해결합니다. 데이터에 대해 복잡한 쿼리를 실행해야 하거나, 많은 관계가 있는 거대한 개체 그래프를 처리해야 하거나 실행 취소 및 다시 실행을 지원해야 하는 경우 Core Data가 매우 적합합니다.
그러나 여기에는 몇 가지 문제점이 있습니다.
아직도 상용구가 많이 남아 있습니다. 관리 객체는 위에 표시된 상용구 중 일부를 줄이지만 Core Data에는 자체적인 내용이 많이 있습니다. 핵심 데이터 스택(영구 저장소 및 영구 저장소 코디네이터 포함)을 올바르게 설정하고 가져오기를 실행하려면 많은 코드 줄이 필요할 수 있습니다.
제대로하기가 어렵습니다. 숙련된 개발자라도 Core Data를 사용할 때 실수를 할 수 있으며 프레임워크는 이를 용서하지 않습니다.
일부 JSON 개체에 액세스하려는 경우 Core Data는 이득은 거의 없이 많은 작업을 수행할 수 있습니다.
그럼에도 불구하고 이미 앱에서 Core Data를 사용하고 있거나 사용하려는 경우 Mantle은 여전히 API와 관리되는 모델 개체 간의 편리한 변환 레이어가 될 수 있습니다.
MTL모델을 입력합니다. GHIssue
MTLModel
에서 상속받은 모습은 다음과 같습니다.
typedef 열거형 : NSUInteger { GHIssueStateOpen, GHIssueStateClosed } GHIssueState;@interface GHIssue : MTLModel <MTLJSONSerializing>@property(비원자, 복사, 읽기 전용) NSURL *URL;@property(비원자, 복사, 읽기 전용) NSURL *HTMLURL;@property(비원자, 복사, 읽기 전용) NSNumber *number; @property(비원자적, 할당, 읽기 전용) GHIssueState 상태;@property (비원자, 복사, 읽기 전용) NSString *reporterLogin;@property (비원자, 강력, 읽기 전용) GHUser *signee;@property (비원자, 복사, 읽기 전용) NSDate *updatedAt;@property (비원자, 복사) NSString *title;@property (비원자, 복사) NSString *body;@property (비원자, 복사, 읽기 전용) NSDate *검색됨At;@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",@"할당자": @"할당자",@"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 *)signeeJSONTransformer {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]; }]; } - (인스턴스 유형)initWithDictionary:(NSDictionary *)dictionaryValue 오류:(NSError **)error { self = [super initWithDictionary:dictionaryValue error:error];if (self == nil) return nil;// 초기화 시 로컬에서 결정해야 하는 값을 저장합니다._retrievedAt = [NSDate date];return self; }@끝
특히 이 버전에는 <NSCoding>
, <NSCopying>
, -isEqual:
및 -hash
구현이 없습니다. MTLModel
하위 클래스에 있는 @property
선언을 검사하여 이러한 모든 메서드에 대한 기본 구현을 제공할 수 있습니다.
원래 예제의 문제도 모두 해결되었습니다.
서버의 새 데이터로
GHIssue
업데이트할 방법이 없습니다.
MTLModel
에는 확장 가능한 -mergeValuesForKeysFromModel:
메서드가 있어 새 모델 데이터를 통합하는 방법을 쉽게 지정할 수 있습니다.
GHIssue
다시 JSON으로 변환할 수 있는 방법은 없습니다.
이것이 가역 변압기가 실제로 유용한 곳입니다. +[MTLJSONAdapter JSONDictionaryFromModel:error:]
<MTLJSONSerializing>
준수하는 모든 모델 객체를 JSON 사전으로 다시 변환할 수 있습니다. +[MTLJSONAdapter JSONArrayFromModels:error:]
는 동일하지만 모델 객체 배열을 JSON 사전 배열로 변환합니다.
향후
GHIssue
의 인터페이스가 변경되면 기존 아카이브가 손상될 수 있습니다.
MTLModel
보관에 사용된 모델 객체의 버전을 자동으로 저장합니다. 보관을 취소할 때 재정의된 경우 -decodeValueForKey:withCoder:modelVersion:
이 호출되어 이전 데이터를 업그레이드할 수 있는 편리한 후크를 제공합니다.
JSON에서 모델 객체를 직렬화하려면 MTLModel
하위 클래스에 <MTLJSONSerializing>
구현해야 합니다. 이를 통해 MTLJSONAdapter
사용하여 모델 개체를 JSON에서 다시 JSON으로 변환할 수 있습니다.
NSError *오류 = nil; XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class fromJSONDictionary:JSONDictionary 오류:&오류];
NSError *error = nil;NSDictionary *JSONDictionary = [MTLJSONAdapter JSONDictionaryFromModel:사용자 오류:&오류];
+JSONKeyPathsByPropertyKey
이 메서드에서 반환된 사전은 모델 개체의 속성이 JSON 표현의 키에 매핑되는 방식을 지정합니다. 예를 들면 다음과 같습니다.
@interface XYUser : MTLModel@property(읽기 전용, 비원자, 복사) NSString *name;@property(읽기 전용, 비원자, 강력) NSDate *createdAt;@property(읽기 전용, 비원자, 할당, getter = isMeUser) BOOL meUser;@property( 읽기 전용, 비원자적, 강력함) XYHelper *helper;@end@implementation XYUser+ (NSDictionary *)JSONKeyPathsByPropertyKey {return @{@"name": @"name",@"createdAt": @"created_at"}; } - (인스턴스 유형)initWithDictionary:(NSDictionary *)dictionaryValue 오류:(NSError **)error { self = [super initWithDictionary:dictionaryValue 오류:오류];if (self == nil) return nil; _helper = [XYHelper helperWithName:self.namecreatedAt:self.createdAt];return self; }@끝
이 예에서 XYUser
클래스는 Mantle이 다양한 방식으로 처리하는 네 가지 속성을 선언합니다.
name
JSON 표현에서 동일한 이름의 키에 매핑됩니다.
createdAt
해당 스네이크 케이스로 변환됩니다.
meUser
JSON으로 직렬화되지 않습니다.
helper
JSON 역직렬화 후에 정확히 한 번 초기화됩니다.
모델의 슈퍼클래스가 매핑을 병합하기 위해 MTLJSONSerializing
도 구현하는 경우 -[NSDictionary mtl_dictionaryByAddingEntriesFromDictionary:]
사용하세요.
Model 클래스의 모든 속성을 자체적으로 매핑하려면 +[NSDictionary mtl_identityPropertyMapWithModel:]
도우미 메서드를 사용할 수 있습니다.
+[MTLJSONAdapter modelOfClass:fromJSONDictionary:error:]
사용하여 JSON을 역직렬화할 때 속성 이름에 해당하지 않거나 명시적인 매핑이 있는 JSON 키는 무시됩니다.
NSDictionary *JSONDictionary = @{@"name": @"john",@"created_at": @"2013/07/02 16:40:00 +0000",@"plan": @"lite"}; XYUser *user = [MTLJSONAdapter modelOfClass:XYUser.class fromJSONDictionary:JSONDictionary 오류:&오류];
여기서 plan
XYUser
속성 이름과 일치하지 않으며 +JSONKeyPathsByPropertyKey
에 매핑되지도 않으므로 무시됩니다.
+JSONTransformerForKey:
JSON에서 역직렬화할 때 다른 유형의 속성을 변환하려면 이 선택적 메서드를 구현하세요.
+ (NSValueTransformer *)JSONTransformerForKey:(NSString *)key { if ([key isEqualToString:@"createdAt"]) { return [NSValueTransformer valueTransformerForName:XYDateValueTransformerName]; } return nil; }
key
모델 객체에 적용되는 키입니다. 원래 JSON 키가 아닙니다. +JSONKeyPathsByPropertyKey
사용하여 키 이름을 변환하는 경우 이 점을 염두에 두십시오.
편의를 위해 +<key>JSONTransformer
구현하면 MTLJSONAdapter
해당 메서드의 결과를 대신 사용합니다. 예를 들어 JSON에서 일반적으로 문자열로 표시되는 날짜는 다음과 같이 NSDate
로 변환될 수 있습니다.
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]; }]; }
변환기가 가역적이면 개체를 JSON으로 직렬화할 때도 사용됩니다.
+classForParsingJSONDictionary:
클래스 클러스터를 구현하는 경우 이 선택적 메서드를 구현하여 JSON에서 개체를 역직렬화할 때 사용해야 하는 기본 클래스의 하위 클래스를 결정합니다.
@interface XYMessage: MTLModel@end@interface XYTextMessage: XYMessage@property(읽기 전용, 비원자, 복사) NSString *body;@end@interface XYPictureMessage: XYMessage@property(읽기 전용, 비원자, 강력) NSURL *imageURL;@end@implementation XYMessage+ (클래스)classForParsingJSONDictionary:(NSDictionary *)JSONDictionary {if (JSONDictionary[@"image_url"] != nil) {return XYPictureMessage.class; }if (JSONDictionary[@"body"] != nil) {return XYTextMessage.class; }NSAssert(NO, @"JSON 사전 '%@'에 대해 일치하는 클래스가 없습니다.", JSONDictionary);return self; }@끝
그런 다음 MTLJSONAdapter
전달한 JSON 사전을 기반으로 클래스를 선택합니다.
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];
맨틀은 자동으로 개체를 유지하지 않습니다. 그러나 MTLModel
<NSCoding>
을 따르므로 NSKeyedArchiver
사용하여 모델 객체를 디스크에 보관할 수 있습니다.
더 강력한 것이 필요하거나 전체 모델을 한 번에 메모리에 유지하지 않으려면 Core Data가 더 나은 선택일 수 있습니다.
Mantle은 다음 플랫폼 배포 대상을 지원합니다.
맥OS 10.10+
iOS 9.0+
tvOS 9.0+
watchOS 2.0+
애플리케이션에 맨틀을 추가하려면:
Mantle 저장소를 애플리케이션 저장소의 하위 모듈로 추가합니다.
Mantle 폴더 내에서 git submodule update --init --recursive
실행합니다.
Mantle.xcodeproj
애플리케이션의 Xcode 프로젝트에 끌어다 놓습니다.
애플리케이션 타겟의 "General" 탭에서 "Embedded Binaries"에 Mantle.framework
추가하세요.
대신 Mantle을 자체적으로 개발하는 경우 Mantle.xcworkspace
파일을 사용하세요.
Cartfile
에 Mantle을 추가하기만 하면 됩니다.
github "Mantle/Mantle"
Mantle을 사용하려는 빌드 대상 아래의 Podfile
에 추가하십시오.
target 'MyAppOrFramework' do pod 'Mantle' end
그런 다음 터미널 또는 CocoaPods 앱 내에서 pod install
실행하세요.
애플리케이션을 작성하는 경우 Xcode 내에서 직접 프로젝트 종속성에 Mantle을 추가하세요.
맨틀을 종속성으로 요구하는 패키지를 작성하는 경우 이를 Package.swift
매니페스트의 dependencies
목록에 추가하세요. 예를 들면 다음과 같습니다.
dependencies: [ .package(url: "https://github.com/Mantle/Mantle.git", .upToNextMajor(from: "2.0.0")) ]
맨틀은 MIT 라이센스에 따라 출시됩니다. LICENSE.md를 참조하세요.
질문이 있나요? 이슈를 열어주세요!