ZManagedObjectStorage で Core Data をちょっとだけ便利に

# ZManagedObjectStorage

今回は、Core Data をより便利に使う `ZManagedObjectStorage` を紹介したいと思います。Core Data を使うためには、Xcode が Core Data 対応のプロジェクトを吐き出したコードの中にあるように、`NSManagedObjectModel` から `NSPersistentStoreCoordinator` さらに `NSManagedObjectContext` の生成へと、結構な量のおきまりのコードを書かなくてはなりません。`ZManagedObjectStorage` を使えばそんなボイラープレートなコードを書く必要がなく、より簡単に Core Data を扱う事ができます。

以下に典型的な Core Data の storage を用意するコードを示します。渡す情報は Core Data のドキュメントのURLと、CoreData のモデルの名前です。指定されたURLにCore Data のファイルがない場合には新たに作られます。

```.swift
let documentURL = ...
let storage = ZManagedObjectStorage(fileURL: documentURL, modelName: "MyModel")
```

あとは、managed object context は`managedObjectContext`プロパティをアクセスすればあとは、通常の Core Data のようにオブジェクトを Fetch したり Insert したりできます。

```.swift
let managedObjectContext = storage.managedObjectContext
do {
	let fetchRequest = NSFetchRequest()
	let entityDescription = NSEntityDescription.entityForName("YourEntity", inManagedObjectContext: managedObjectContext)
	fetchRequest.entity = entityDescription
	fetchRequest.sortDescriptors = [NSSortDescriptor(key: "some_key", ascending: true)]
	let objects = try managedObjectContext.executeFetchRequest(fetchRequest)
	// ...
}
catch let error {
	print("\(error)")
}
```

```.swift
let managedObjectContext = storage.managedObjectContext
do {
	let managedObject = NSEntityDescription.insertNewObjectForEntityForName("YourEntity", inManagedObjectContext: managedObjectContext)
	try managedObject.save()
}
catch let error {
	print("\(error)")
}
```

# なぜ モデル名を指定するのですか？

Xcode が Core Data 対応のプロジェクトを作成する際は全てのモデルをマージした状態で、managed object context を用意しています。多くの場合はこれで事足りるかもしれませんが、例えば、関連性の低い二つのデータを異なるドキュメントとして、Core Data で扱おうとすると、おそらく二つのモデルを用意しようと考えるのが自然だと思いますが、一方のデータを Core Data で保存したとしても、他方のモデル情報もその Core Data ファイルに含まれしまいます。

例えば、商品情報のCore Dataファイルと、株価情報のCore Dataファイルを同じアプリで扱う場合に、マージされたモデルを使うと、商品情報の Core Data ファイルにも株価情報のスキーマが、株価情報を扱う Core Data ファイルにも商品情報のスキーマが紛れんでしまいます。

したがって、この状態で商品情報のスキーマの変更があると、株価情報のCore Dataファイルにも影響を与えてしまいます。その逆も然りです。このように、二つのスキーマの関連性がない場合などには、モデルを分けて扱う方がマイグレーションなども含めて扱いやすくなると考えるのが自然なので、`ZManagedObjectStorage`では、モデルを別に指定するようにしています。

## ソース

```ZManagedObjectStorage.swift
import Foundation
import CoreData


class ZManagedObjectStorage {

	var fileURL: NSURL
	var modelName: String

	init?(fileURL: NSURL, modelName: String) {
		self.fileURL = fileURL
		self.modelName = modelName
		if self.managedObjectContext == nil {
			return nil
		}
	}
	
	lazy var managedObjectModel: NSManagedObjectModel? = {
		var managedObjectModel: NSManagedObjectModel? = nil
		let modelURL = NSBundle.mainBundle().URLForResource(self.modelName, withExtension: "momd")
		if  modelURL != nil {
			managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL!)
		}
		if managedObjectModel == nil { NSLog("model not found: name=%@", self.modelName) }
		return managedObjectModel
	}()

	lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
		var coordinator: NSPersistentStoreCoordinator? = nil
		var error: NSError? = nil

		var managedObjectModel = self.managedObjectModel
		if (managedObjectModel != nil) {

			do {
				coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!)
				let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true];
				let store = try coordinator?.addPersistentStoreWithType(NSSQLiteStoreType,
							configuration:nil, URL:self.fileURL, options:options)
				if store == nil {
					NSLog("Failed migrating Core Data Model. Old file will be deleted.")
					NSLog("file: %@", self.fileURL.path!)

					#if DEBUG
					NSFileManager.defaultManager().removeItemAtPath(self.file, error: &error)
					ZReportError(error)
					// recreate store file once again
					store = coordinator.addPersistentStoreWithType(NSSQLiteStoreType,
								configuration:nil, URL:self.storeURL, options:options, error:&error)
					ZReportError(error)
					if store == nil { return nil }
					#endif
				}
			}
			catch let error {
			}
		}
		if coordinator == nil {
			NSLog("failed to configure persistent store coodinator: %@", self.fileURL.path!)
		}
		return coordinator
	}()

	lazy var managedObjectContext: NSManagedObjectContext? = {
		var context: NSManagedObjectContext? = nil
		let coordinator = self.persistentStoreCoordinator
		if coordinator != nil {
			context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
			context?.persistentStoreCoordinator = self.persistentStoreCoordinator
		}
		return context
	}()

}
```

同コードはgistからも取得可能です。
https://gist.github.com/codelynx/2fb57bf3f8ea3701b1af57ed2f851cc5

# GitHub

また、Xcode のサンプルプロジェクトも用意いたしました。サンプルプロジェクトでは iOS で `+` をタップすると乱数を発生させタイムスタンプと共に、Core Data に保存するというものです。実用性はありませんが、どのように`ZManagedObjectStorage` を使えば良いのかご確認いただけるようになっています。

![Simulator-Screen-Shot-Jun-19,-2016,-3.17.58-AM-2.png](https://qiita-image-store.s3.amazonaws.com/0/65634/83c62358-3b5c-63de-8e14-e3786c7cc364.png)


https://github.com/codelynx/ZManagedObjectStorage

［環境表示］

```.console
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
Xcode 7.3 (7D175)
iOS 9.3 & OS X El Capitan 10.11
```