背景
サーバーAPIで返ってくるjsonを、CoreDataのオブジェクトにマッピングして保存したかった。
今回いくつかライブラリを検討した結果、EasyMappingが利用しやすそうだったので、採用することにした。
EasyMappingとCoreDataの連携方法
基本的なEasyMappingの使い方はgithubのREADMEを見るのが一番分かりやすいと思う。
CocoaPodsでもインストールできるし、Swiftでの利用例も公式から提供されている。
ただ、CoreDataオブジェクトとのマッピングの例はほとんどなかったのでここではそちらについて書こうと思う。
エンティティの定義
CoreDataで利用するエンティティは普通NSManagedObject
のサブクラスとなるが、EasyMappingに対応するにはEKManagedObjectModel
を継承する。
今回のサンプルでは3つのプロパティを持った以下のようなエンティティを定義した。
import Foundation
import CoreData
@objc(Profile)
class Profile: EKManagedObjectModel {
@NSManaged var id: NSNumber
@NSManaged var userName: String
@NSManaged var birthday: NSDate
}
マッピングの定義はobjectMapping
メソッドをオーバーライドする。
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
}
}
マッピングの実行
上記のような準備をすれば、以下のようなサンプルが実行できる。
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点を反映したサンプル
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
}
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