JSON<->NSManagedObjectのマッピングを簡単に！Mantleを使ってCore Dataのソースコードをシンプルにする


## 概要
JSONをProperyに変換する作業を一つずつ手動でコーティングするのはかなり手間だと思います。
その作業を楽にするために今回使用する[Mantle](https://github.com/MantleFramework/Mantle)やその他に[JSONModel](https://github.com/icanzilb/JSONModel)などを使ってマッピングを極力自動でできるようにしている方は多いのではないでしょうか？

この便利な仕組みをCore Dataの`NSManagedObject`でも利用できないかなと思い調べてみた際のログです。
結論から言うとMantleは既に`NSManagedObject`にも対応しています。

これができれば例えばサーバからJSONで受け取った値を自動で`NSManagedObject`にマッピングして登録することがとても簡単になります。

今回利用する`NSManagedObject`のレイアウトを記載しておきます。

エンティティ名:Blog
クラス名:ASBlogEntity

|項目名|データ型|
|:-----------|:------------|
|title  |NSString |
|body|NSString|
|pubDate|NSDate|
|userID|NSString|
|webURL|NSString|
|blogImageURL|NSString|

Mantleで使用するモデルクラスのレイアウトです。

クラス名:ASBlogModel

|項目名|データ型|
|:-----------|:------------|
|title  |NSString |
|body|NSString|
|pubDate|NSDate|
|creator|NSString|
|webURL|NSString|
|blogImageURL|NSURL|

ポイントとして項目名が違うuserID、creatorの関連付けを手動でマッピングし、`blogImageURL`の型の違いを`NSValueTransformer`で対応します。

## 手順
ではMantleの導入からです。

下記内容のPodfileを作成してインストールしましょう。

```
platform :ios, '7.0'
pod 'Mantle'
```

`pod install`を実行することも忘れずに。

Core Dataに関する詳細な設定は省いて、検証した内容のみ記載しておきます。

まずCore DataのData ModelにBlogエンティティを作成します。(Blogエンティティのクラス名はASBlogEntityに設定)
次にNSManagedObject subclassを作成し、ASBlogEntityクラスを自動生成します。
これでCore Data側の準備はオッケーです。
下記は自動生成されたASBlogEntityのソースとなります。

```objc
@interface ASBlogEntity : NSManagedObject

@property (nonatomic, retain) NSString * blogImageURL;
@property (nonatomic, retain) NSString * body;
@property (nonatomic, retain) NSDate * pubDate;
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSString * userID;
@property (nonatomic, retain) NSString * webURL;

@end

@implementation ASBlogEntity

@dynamic blogImageURL;
@dynamic body;
@dynamic pubDate;
@dynamic title;
@dynamic userID;
@dynamic webURL;

@end
```

次はMantleを使用したModelクラスの作成です。

こちらはJSONの項目に基づいて手動で作成しています。
今回作成した内容は下記の通りです。

```objc
@interface ASBlogModel : MTLModel <MTLJSONSerializing, MTLManagedObjectSerializing>

@property (nonatomic, strong) NSString *title;

@property (nonatomic, strong) NSString *body;

@property (nonatomic, strong) NSString *creator;

@property (nonatomic, strong) NSURL *blogImageURL;

@property (nonatomic, strong) NSString *webURL;

@property (nonatomic, strong) NSDate *pubDate;

@end


@implementation ASBlogModel

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"body": @"description",
             @"creator": @"dc:creator",
             @"blogImageURL": @"media:content.url",
             @"webURL": @"link",
             };
}

+ (NSDictionary *)managedObjectKeysByPropertyKey {
    return @{
             @"creator": @"userID",
             };
}

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

// 画像URLはJSONではNSURLだがNSManagedObjectはNSStringのため型変換を手動で行う
+ (NSValueTransformer *)blogImageURLEntityAttributeTransformer {
    return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSURL *url) {
        return url.absoluteString;
    } reverseBlock:^(NSString *str) {
        return [NSURL URLWithString:str];
    }];
}

+ (NSString *)managedObjectEntityName {
    // クラス名ではなくData Modelで定義したエンティティ名を返す
    return @"Blog";
}

@end

```

`+ managedObjectKeysByPropertyKey:`をオーバーライドしてモデルのプロパティと`NSManagedObject`のプロパティのマッピングを行います。
同名のプロパティは自動でマッピングしてくれますが、プロパティ名が異なる項目はここで関連付けを行います。
しかし全て同名の項目の場合でも一つは項目を定義しないとマッピングがうまくいきません。
例えば空の`NSDictionary`やnilを返すとマッピングに失敗します。
これはさすがに不便なので今後の改善に期待するか自前で修正するかのどちらかになるでしょう。
説明は省きますが`+ JSONKeyPathsByPropertyKey:`は`+ managedObjectKeysByPropertyKey:`メソッドのJSON版という認識でオッケーです。

プロパティ名ではなくデータ型が異なるケースではblogImageURLプロパティのように`+ blogImageURLEntityAttributeTransformer:`メソッドに返す結果を直接書くことで対応します。
(EntityAttributeTransformerより前がプロパティ名になります)

下記のようにテストを行ってみました。

```objc
ASBlogModel *blog = [[ASBlogModel alloc] init];
    
blog.title = @"title1";
blog.body = @"body1";
blog.creator = @"asakahara";
blog.webURL = @"http://www.mocology.com";
blog.blogImageURL = [NSURL URLWithString:@"http://www.mocology.com"];
    
ASBlogEntity *blogEntity = [MTLManagedObjectAdapter managedObjectFromModel:blog
                                                                insertingIntoContext:self.managedObjectContext error:nil];
    
NSLog(@"blogEntity: %@", blogEntity);
```

テスト結果は下記の通りで、マッピングは問題なく成功しています。

```
blogEntity: <ASBlogEntity: 0x8c335e0> (entity: Blog; id: 0x8c33800 <x-coredata:///Blog/t626186A9-E3A2-487E-9EFB-A224A4C63FD52> ; data: {
    blogImageURL = "http://www.mocology.com";
    body = body1;
    pubDate = nil;
    title = title1;
    userID = asakahara;
    webURL = "http://www.mocology.com";
})
```

## まとめ

Mantleを使用することでJSONだけでなくNSManagedObjectのマッピングもとてもシンプルにコーディングできるようになりました。
自前でマッピング処理を記述するよりも機能も豊富なのでMantleを使用することをお勧めします。