[Objective-C] CoreDataを使う


マイグレーションに関しては[別記事に移動](http://qiita.com/edo_m18/items/717fe32d744a10df7179)しました。

-------------

なんとなくCoreDataを理解してきたのでメモ。
CoreDataはO/Rマッピングフレームワークで、Objective-C内のオブジェクトをRDBなどのストアとマッピングしてデータを永続化できる仕組み（であってるかな？）

ざっくり言ってしまえば、SQLiteをObjective-Cから扱うための便利クラスを集めたフレームワーク。
（もちろん、SQLite以外のストアもあるからあくまでイメージ）
なので、様々なクラスが関わりあいながら、DBからデータを取得したり保存したり、といったことが可能となっている。

そもそもDBにはそれほど詳しくないので、間違っていたらバシバシコメントもらいたいんですが、ざっくり言うとCoreDataの用語とRDBの用語は以下の関連性になっているみたい。

| CoreData | RDB |
|----------|--------|
| エンティティ | テーブル |
| 管理オブジェクト（NSManagedObject） | レコード |
| 管理オブジェクトコンテキスト（NSManagedObjectContext） | クエリ？ |
| 管理オブジェクトモデル（NSManagedObjectModel） | DB |

##使う前の準備

まず、CoreDataを使うためにいくつかのインスタンスを生成しておく必要があります。
`NSManagedObjectModel`、`NSPersistentStoreCoordinator`、`NSManagedObjectContext`の3つです。

1. `NSManagedObjectModel`を定義を元に生成
2. `NSManagedObjectModel`のインスタンスを元に`NSPersistentStoreCoodinator`クラスを生成（ちなみに和訳は「永続化ストア」）
3. CoreDataで使うSQLiteのファイルを取得
4. 生成した`NSPersistentStoreCoodinator`インスタンスを`NSSQLiteStoreType`に指定し、(3)で取得したファイルのURLを指定してセットアップ
5. (4)までで生成した`NSPersistentStoreCoodinator`を元に、`NSManagedObjectContext`インスタンスを生成

use core dataのチェックを入れて自動生成されたコードを参考にすると、以下のようにアクセサメソッド内でインスタンスを生成している。

```objc
- (NSManagedObjectModel *)managedObjectModel
{
	if (_managedObjectModel != nil) {
		return _managedObjectModel;
	}

	NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"HogeModel" withExtension:@"momd"];
	_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
	
	return _managedObjectModel;
}
```

```objc
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
	if (_persistentStoreCoordinator) {
		return _persistentStoreCoordinator;
	}

	NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"HogeProj.splite"];

	NSError *error = nil;
	_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
	if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}

	return _persistentStoreCoordinator;
}
```

```objc
- (NSManagedObjectContext *)managedObjectContext
{
	if (_managedObjectContext != nil) {
		return _managedObjectContext;
	}

	NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
	if (coordinator != nil) {
		_managedObjectContext = [[NSManagedObjectContext alloc] init];
		[_managedObjectContext setPersistentStoreCoordinator:coordinator];
	}

	return _managedObjectContext;
}
```

##CoreDataを使う

前述までの処理で使う準備が整いました。
自分の理解では、
`NSManagedObjectModel`はモデルデータ全体の設定。つまりDBスキーマ。
`NSPersistentStoreCoodinator`は上記DB役との仲介役。コーディネータ。
`NSManagedObjectContext`は、上記とアプリケーションをつなぐ役。なので、基本的にはセットアップが終わったら、アプリケーション上では`NSManagedObjectContext`を介してデータのやりとりを行います。

実際の（いわゆるMVCのModelとしての）モデルデータについてはCoreDataのモデルエディタ（？）を使ってXcode上でさくっと設定が作れます。それを元にインスタンスを生成する、という感じです。

###CoreDataを使う手順

1. NSFetchRequestインスタンスを生成
2. NSManagedObjectContextインスタンスを介してモデルデータを取得する。
3. (2)で`NSManagedObject`のインスタンスの配列が返される（モデルデータを操作するラッパー的なクラス？）
4. 取得した`NSManagedObject`を介してデータの更新などをする
5. 操作完了後に`[managedObjectContext save:&error];`を実行して、操作したモデルデータを永続化ストアに伝えて更新する

ちょーざっくり書くとこんな感じ。
要は、`NSManagedObjectContext`を介してモデルデータを取得し、`NSManagedObject`を使ってデータの更新を行う、ということです。

実際のサンプルコードは以下。

```objc
//fetch設定を生成
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([HogeData class])];

//sort条件を設定
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

//ARCオフの場合はreleaseする
//[sortDescriptor release];
//[sortDescriptors release];

//fetch設定を元に、managedObjectContextからデータを取得
//返り値は`NSManagedObject`の配列になる
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];

for (NSManagedObject *data in results) {
	//do something.
}
```


###CoreDataで新規データを生成する

```objc
HogeData *hogeData = (HogeData *)[NSEntityDescription insertNewObjectForEntityForName:@"HogeData" inManagedObjectContext:managedObjectContext];
```

なんだかややこいですが、`HogeData`はXcodeのモデルエディタで操作するエンティティの名前です。
実体は`NSManagedObject`クラスを継承しているサブクラスになります。

これらの情報と、managedObjectContextから新規データを生成します。
生成には`NSEntityDescription`クラスの`insertNewObjectForEntity:inManagedObjectContext:`メソッドを使います。（`[[HogeData alloc] init]`はしない）

生成してすぐに、いわゆるテーブルに新規レコードが追加されます。
返された`NSManagedObject`サブクラスのインスタンスから各種情報を設定し、最後にcontextのsave:メソッドを実行して更新すればめでたく新規データの追加が完了します。

###CoreDataのデータ削除

データの削除には、`NSManagedObjectContext`の`deleteObject:`メソッドを利用します。
実際のコード例は以下。

```objc
//削除対象のフェッチ情報を生成
NSFetchRequest *deleteRequest = [[NSFetchRequest alloc] init];
[deleteRequest setEntity:[NSEntityDescription entityForName:@"HogeData" inManagedObjectContext:managedObjectContext]];
[deleteRequest setIncludesPropertyValues:NO]; //managed object IDのみフェッチ

NSError *error = nil;

//生成したフェッチ情報からデータをフェッチ
NSArray *results = [managedObjectContext executeFetchRequest:deleteRecest error:&error];
//[deleteRequest release]; //ARCオフの場合

//フェッチしたデータを削除処理
for (NSManagedObject *data in results) {
	[managedObjectContext deleteObject:data];
}

NSError *saveError = nil;

//削除を反映
[managedObjectContext save:&saveError];
```
参考にした記事 -> [Core Data: Quickest way to delete all instances of an entity - stackoverflow](http://stackoverflow.com/questions/1383598/core-data-quickest-way-to-delete-all-instances-of-an-entity)

-----------------------

##アプリで保存可能なディレクトリ情報を取得する
アプリ単位で保存できる領域が決まっており、そのディレクトリ情報を取得する方法。
（ちょっと勘違いがあるかも。とりあえずのメモです）

まず、CoreDataを使うようにするとAppDelegate.mに定義されているソース。

```objc
- (NSURL *)applicationDocumentsDirectory
{
	NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
	return url;
}
```

NSFileManagerを使って、該当のディレクトリを`NSURL`型で取得して返しています。


## CoreDataに保存されている件数を取得する

追加ロード時など、現在CoreDataに何件のデータが保持されているかを知りたいケースがあるかと思います。
その場合には`NSManagedObjectContext#countForFetchRequest`を利用します。

```objc
// Entityを指定してFetchRequestを作る
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClassName(anEntityClass.class)];
request.includesSubentities = NO;

NSError *error = nil;

AppDelegate *delegate = UIApplication.sharedApplication.deleate;

// NSManagedObjectContextから、生成したリクエストを元に件数だけを取得
// （NSManagedObjectContextは適宜定義されているものに置き換えてください）
NSInteger count = [delegate.managedObjectContext countForFetchRequest:request error:&error];

// もし見つからなければ0件
if (count == NSNotFound) {
	return 0;
}

return count;
```