LoginSignup
11
11

More than 5 years have passed since last update.

SwiftでjsonをCoreData(NSManagedObject)にマッピングするならEasyMappingがいいんじゃないの?

Posted at

背景

サーバーAPIで返ってくるjsonを、CoreDataのオブジェクトにマッピングして保存したかった。
今回いくつかライブラリを検討した結果、EasyMappingが利用しやすそうだったので、採用することにした。

EasyMappingとCoreDataの連携方法

基本的なEasyMappingの使い方はgithubのREADMEを見るのが一番分かりやすいと思う。
CocoaPodsでもインストールできるし、Swiftでの利用例も公式から提供されている。

ただ、CoreDataオブジェクトとのマッピングの例はほとんどなかったのでここではそちらについて書こうと思う。

エンティティの定義

CoreDataで利用するエンティティは普通NSManagedObjectのサブクラスとなるが、EasyMappingに対応するにはEKManagedObjectModelを継承する。

今回のサンプルでは3つのプロパティを持った以下のようなエンティティを定義した。

Profile.swift
import Foundation
import CoreData

@objc(Profile)
class Profile: EKManagedObjectModel {

    @NSManaged var id: NSNumber
    @NSManaged var userName: String
    @NSManaged var birthday: NSDate

}

マッピングの定義はobjectMappingメソッドをオーバーライドする。

Profile+Methods.swift
import Foundation

extension Profile {

    override class func objectMapping() -> EKManagedObjectMapping {
        // マッピングの定義
        var mapping = EKManagedObjectMapping(entityName: "Profile")

        // jsonのkeyと同じプロパティ名のマッピング
        mapping.mapPropertiesFromArray(["id"])

        // jsonのkeyと異なるプロパティ名のマッピング(key名 -> property名)
        mapping.mapPropertiesFromDictionary(["user_name":"userName"])

        // NSDateのマッピング
        var formatter: NSDateFormatter = NSDateFormatter()
        formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd"
        mapping.mapKeyPath("birthday", toProperty: "birthday", withDateFormatter: formatter)

        return mapping        
    }

}

マッピングの実行

上記のような準備をすれば、以下のようなサンプルが実行できる。

sample.swift
func sample(context: NSManagedObjectContext) {

    // マッピングしたいDictionary
    let json: [String:AnyObject] = [
        "id": 1234,
        "user_name": "my name",
        "birthday": "2007-08-31"
    ]

    // Dictionaryからエンティティを作成
    let prof: Profile = Profile.objectWithProperties(json, inContext: context)
    println("id: \(prof.id)")             // id: 1234
    println("userName: \(prof.userName)") // userName: my name
    println("birthday: \(prof.birthday)") // birthday: 2007-08-31 00:00:00 +0000

    // エンティティからDictionaryを作成
    let dic: AnyObject = prof.serializedObjectInContext(context)
    println(dic)
    // {
    //     birthday = "2007-08-31";
    //     id = 1234;
    //     "user_name" = "my name";
    // }
}

「jsonからエンティティの作成」・「エンティティからjsonの作成」が、それぞれ一行だけで簡単に記述できた。
これはマッピングをモデルクラス内で定義しているおかげ。

// Dictionaryからエンティティを作成
let prof: Profile = Profile.objectWithProperties(json, inContext: context)
// エンティティからDictionaryを作成
let dic: AnyObject = prof.serializedObjectInContext(context)

応用編

1. エンティティの更新

先の例のようにobjectWithPropertiesを利用すると、常に新しいエンティティが作成されてしまう。
既に存在するエンティティのプロパティを更新したい場合は、EKManagedObjectMapper.fillObjectを利用すれば良い。

        let json1: [String:AnyObject] = ["user_name": "new name"]
        EKManagedObjectMapper.fillObject(prof, fromExternalRepresentation: json1,
            withMapping: Profile.objectMapping(), inManagedObjectContext: context)
        println(prof.serializedObjectInContext(context))
        // [id: 1234, user_name: new name, birthday: 2007-08-31]

2. カスタムマッピング

もっと複雑なマッピングを行うこともできる。
Bool型のis_publicというkeyの値を、NSNumber型のisPublicプロパティにマッピングする例を示す。

        // BoolをNSNumberにマッピング
        mapping.mapKeyPath("is_public", toProperty: "isPublic",
            withValueBlock: { (key, value, context) -> AnyObject! in
                return value
        }) { (value, context) -> AnyObject! in
            return value.boolValue // NSNumberをBoolに変換
        }

上記2点を反映したサンプル

Profile.swift
import Foundation
import CoreData

@objc(Profile)
class Profile: EKManagedObjectModel {

    @NSManaged var userName: String
    @NSManaged var id: NSNumber
    @NSManaged var birthday: NSDate
    @NSManaged var isPublic: NSNumber

}
Profile+Methods.swift
import Foundation

extension Profile {

    override class func objectMapping() -> EKManagedObjectMapping {
        // マッピングの定義
        var mapping = EKManagedObjectMapping(entityName: "Profile")

        // jsonのkeyと同じプロパティ名のマッピング
        mapping.mapPropertiesFromArray(["id"])

        // jsonのkeyと異なるプロパティ名のマッピング(key名 -> property名)
        mapping.mapPropertiesFromDictionary(["user_name":"userName"])

        // NSDateのマッピング
        var formatter: NSDateFormatter = NSDateFormatter()
        formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd"
        mapping.mapKeyPath("birthday", toProperty: "birthday", withDateFormatter: formatter)

        // BoolをNSNumberにマッピング
        mapping.mapKeyPath("is_public", toProperty: "isPublic",
            withValueBlock: { (key, value, context) -> AnyObject! in
                return value
        }) { (value, context) -> AnyObject! in
            return value.boolValue
        }

        return mapping
    }

}
func sample(context: NSManagedObjectContext) {

    // マッピングしたいDictionary
    let json: [String:AnyObject] = [
        "id": 1234,
        "user_name": "my name",
        "birthday": "2007-08-31",
        "is_public": true
    ]

    // Dictionaryからエンティティを作成
    let prof: Profile = Profile.objectWithProperties(json, inContext: context)
    println("id: \(prof.id)")             // id: 1234
    println("userName: \(prof.userName)") // userName: my name
    println("birthday: \(prof.birthday)") // birthday: 2007-08-31 00:00:00 +0000
    println("isPublic: \(prof.isPublic)") // isPublic: 1

    // エンティティからDictionaryを作成
    let dic: AnyObject = prof.serializedObjectInContext(context)
    println(dic)
    // {
    //     birthday = "2007-08-31";
    //     id = 1234;
    //     "is_public" = 1;
    //     "user_name" = "my name";
    // }

    // エンティティの更新
    let json1: [String:AnyObject] = ["user_name": "new name", "is_public": false]
    EKManagedObjectMapper.fillObject(prof, fromExternalRepresentation: json1,
        withMapping: Profile.objectMapping(), inManagedObjectContext: context)
    println(prof.serializedObjectInContext(context))
    // [id: 1234, is_public: 0, birthday: 2007-08-31, user_name: new name]

}

感想

マッピングをモデルクラス側に記述できるのは管理がしやすくて良い。しかもエンティティの生成などには、暗黙的にマッピングを利用してくれる。結果として利用する側のソースが簡潔に書ける。
カスタムマッピングの機能もあるので、かなり柔軟にマッピングが実装できる点も重要だ。特にCoreDataの場合は、relationを利用している場合などに重宝しそうである。

現状プロジェクトでは、サーバーとの通信周りはAlamofire、CoreDataの操作にはMagicalRecordを利用しているがEasyMappingとの併用に関して全く支障はない。

検討したオブジェクトマッピングライブラリ

EasyMapping以外に今回候補に上がったライブラリと、検討時のメモ。
実際には利用していないものもあるので、もしかするとEasyMappingより良いものがあるかもしれない。

  • Mantle/Mantle
    Objective-Cではよく利用されているライブラリ。
    ただ、Swiftでは動かないという情報もあるので今回は避けたかった。
  • dchohfi/KeyValueObjectMapping
    公式以外情報ないが、利用は簡単そう。
  • Hearst-DD/ObjectMapper
    Swiftで書かれているようなのでSwiftで利用するなら安心か。
    でもNSManagedObject(CoreData)で利用できるかは分からない。
  • EasyMapping/EasyMapping
    最終更新日が新しいのでメンテナンスはされていそう。
    CoreDataにも対応してるっぽいし、試してみよう。
  • mutualmobile/MMRecord
    サーバーへのリクエストも含めて利用できる。
    SwiftやCoreDataの例も載ってるが、正しく動かないというIssueが上がってるのが懸念点。

参考

EasyMapping/EasyMapping
https://github.com/EasyMapping/EasyMapping

dchohfi/KeyValueObjectMapping
https://github.com/dchohfi/KeyValueObjectMapping

Hearst-DD/ObjectMapper
https://github.com/Hearst-DD/ObjectMapper

Mantle/Mantle
https://github.com/Mantle/Mantle

mutualmobile/MMRecord
https://github.com/mutualmobile/MMRecord

Alamofire/Alamofire · GitHub
https://github.com/Alamofire/Alamofire

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11