Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

概要

JSONをProperyに変換する作業を一つずつ手動でコーティングするのはかなり手間だと思います。
その作業を楽にするために今回使用するMantleやその他に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のソースとなります。

@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の項目に基づいて手動で作成しています。
今回作成した内容は下記の通りです。

@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より前がプロパティ名になります)

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

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を使用することをお勧めします。

asakahara
仕事でも趣味でもアプリ作ってます。 最近はこちらのアプリを中心に開発しています。 https://mocology.com/ja/milktime/
http://sakahara.hatenablog.jp
sonicmoov
ソニックムーブは最先端のテクノロジーと サービス運用から得たデータで、 企業の『課題解決』『資産の最大化』『新規事業創出』を 支援するR&Dスタジオです。
https://www.sonicmoov.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away