Mogenerator + MagicalRecordでCoreData入門


初投稿。iOSアプリ開発ビギナーで至らないところもあるかと思いますがよろしくお願いします（ツッコミとか）。

----

データの永続化を行うにあたり、iOSではフラットファイル、SQLite、CoreDataを使うことが出来ます。前者2つはウェブ・アプリケーションで多少馴染みがあるものの、Cocoaプラットフォームでのデータ永続化はさっぱりわからずCoreDataって何？という状態からスタートしました。

このドキュメントでカバーすること:

* CoreData, mogenerator, MagicalRecordがどんなものであるかとその使い方

このカバーしないこと:

* Xcodeの基本的な使い方
* CoreData, mogenerator, MagicalRecord以外のライブラリのインストール方法

----

# 登場人物

* [CoreData](https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coredata/cdProgrammingGuide.html)
* [mogenerator](http://rentzsch.github.io/mogenerator/)
* [MagicalRecord](https://github.com/magicalpanda/MagicalRecord)

## CoreData

CoreDataプログラミングガイドより。

>Core Dataフレームワークは、オブジェクトの永続性をはじめ、ライフサイクルやオブジェクトグラフの管理に関連して、頻繁に必要となるさまざまな処理機能を、汎用化、自動化して提供します。

Appleは **CoreDataはORMではないと明言しています** が、実際のところデータベースの一種（関連データベース=Relational Databaseではない）であり、プログラムからモデルクラスを通して操作する点はORMの概念と似通っています。

例えばPython / Djangoではモデルの作成を次のように行いますが、

```python
bike = Bike()
bike.brand = u"Ravanello"
bike.name = u"SAT"
bike.save()
```

CoreData + MagicalRecordでは次のように行います。

```objectivec
Bike *bike = [Bike MR_createEntity];
bike.brand = @"Ravanello";
bike.name = @"SAT";

[[NSManagedObjectContext MR_context] MR_saveToPersistentStoreAndWait]; // この辺ちょっとうろ覚え
```

個人的にはほとんど同じ概念で扱えるんじゃないかと思ってます。


## mogenerator

Xcode上でのモデル定義は、モデルエディタによりGUIで行い、NSManagementObjectのサブクラスとして、ヘッダ、実装ファイルに出力することが出来ます。この機能は一見便利なのですが、出力したファイルは次の問題があるように思います。

* Xcodeからファイルを出力すると既存のファイルが上書きされる
* 既存のファイルに実装があった場合、上書きで削除される
* 結果として、モデルクラスに独自の実装を追加することが出来ない（カテゴリ作って拡張したけど悪手すぎた）

mogeneratorはカスタマイズ可能なモデルクラスを作成することを目的として開発されており、Xcodeで作成したモデル定義を元に次の2つのクラスを作成します。

* mogenerator独自の内部クラス
* カスタマイズ用のサブクラス

カスタマイズ用のサブクラスには任意の実装を追加することが出来て、mogeneratorでファイルの再出力を行っても上書きされません。つまり、mogeneratorを使用することでカスタマイズの柔軟性を確保することが出来ます。


## MagicalRecord

[MagicalRecord本家サイト](https://github.com/magicalpanda/MagicalRecord)によれば、Ruby on RailsのActiveRecordにインスパイアされたCoreDataラッパーライブラリである、となります。

このライブラリを使用すると、CoreDataの初期化手順、モデルのフェッチなどを短い記述で行えるようになり、要はCoreDataの面倒くさい手続きを代わってくれるライブラリです（と個人的には考えています）。


# インストール

## mogenerator

公式の配布物からインストールするか、[homebrew](http://brew.sh/)からインストールする。

![kobito.1384834470.045805.png](https://qiita-image-store.s3.amazonaws.com/0/29860/63134463-c63a-42e2-a3ec-8867fcb6fee7.png "kobito.1384834470.045805.png")

brewの場合はbrewコマンドからどうぞ。

<pre><code>brew install mogenerator</code></pre>

## MagicalRecord

CocoaPodsからインストールする。Podfileに以下を書きます。

```ruby
pod 'MagicalRecord'
```

pod installします。

```bash
/usr/bin/pod install
```

インストールすると.pchファイルに次の行が追加されているので確認します。

```objectivec
#import "CoreData+MagicalRecord.h"
```


# 使い方

## モデルの定義

## mogeneratorによるヘッダ、実装ファイル出力

mogeneratorによるファイル出力は、入力にXcodeで作成したモデルファイルを、出力先に適当なディレクトリを指定します。

こんな感じ。

```bash
/usr/bin/mogenerator \
  --template-var arc=true \
  -m ./example/Model/Model.xcdatamodeld \
  -O ./example/Model/
```

オプションを1つずつ超簡単に解説します。

```bash
--template-var arc=true
```

ARC環境で利用するには`--template-var arc=true`を指定します（これ以上のことは調べていません！）。

```bash
-m ./example/Model/Model.xcdatamodeld
```

Xcodeで作成したモデルファイルを指定します。

```bash
-O ./example/Model/
```

ヘッダ、実装ファイルの出力先を指定します。

mogeneratorは機械生成によるmogenerator内部クラスを **machine file**, カスタマイズするためのサブクラスファイルを **human file** と呼んでいます。machine fileはファイル名のプリフィクスに`_`が付いていて、内部クラスとして認識出来る形になっています。一方human fileにはXcodeで定義したモデルクラス名がそのまま使用されています。

例えばモデル定義が`Bike`だとしたら、

* machine file: `_Bike.h`, `_Bike.m`
* human file: `Bike.h`, `Bike.m`

となります。

なお、ファイル管理の都合でmachine file, human fileの出力先を分けたい場合には、`--machine-dir`と`--human-dir`オプションを利用してください。

mogeneratorの実行タイミングは用途によりけりですが、mogeneratorからのファイル出力はbuild phaseの1つとして加えても良いですし、必要なタイミングで都度コマンドラインから実行しても良いと思います。

## MagicalRecordによるCoreDataの初期化

CoreDataの初期化は`application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions`でMagicalRecordのセットアップメソッドを呼び出します。`setupCoreDataStackWithStoreNamed:`メソッドでは、CoreDataのバックエンドとしてSQLiteを使用し、任意のデータベースファイル（ここではdb.sqlite）を作成することを意味します。


```objectivec
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // initialize coredata
    [MagicalRecord setupCoreDataStackWithStoreNamed:@"db.sqlite"];

    // (snip)
}
```

次に、アプリケーション終了時に正しくデータを永続化するため`applicationWillTerminate:(UIApplication *)application`内でMagicalRecordの`cleanUp`メソッドを呼び出します。


```objectivec
- (void)applicationWillTerminate:(UIApplication *)application
{
    [MagicalRecord cleanUp];
}
```



## モデルの作成、読み出し、更新、削除

```objectivec
// create
Bike *bike = [Bike MR_createEntity];

// retrieve
Bike *bike = [Bike MR_findFirstByAttribute:@"id" withValue:@1];

// update
Bike *bike = [Bike MR_findFirstByAttribute:@"id" withValue:@1];
bike.name = @"Colnago";
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait]; // この辺ちょっとうろ覚え

// delete
[bike MR_deleteEntity];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait]; // この辺ちょっとうろ覚え
```


## モデルのカスタマイズ

モデルをカスタマイズする場合には、mogeneratorが出力した **human file** を編集してください。次のファイルは某アプリの写真管理部分のソースコードです。

通常のObjective-Cプログラミングと同様に、ヘッダに宣言をして実装を行います。

Photo.h

```objectivec
#import "_Photo.h"

typedef NS_ENUM(NSUInteger, kMyBikePhotoSize) {
    kMyBikePhotoSizeSmall,
    kMyBikePhotoSizeMedium,
    kMyBikePhotoSizeLarge,
    kMyBikePhotoSizeOriginal
};

@interface Photo : _Photo {}

- (NSURL *)getThumbnailUrlWithSize:(enum kMyBikePhotoSize)photoSize;

@end
```


Photo.m

```objectivec
#import "Photo.h"

@interface Photo ()

// Private interface goes here.

@end

@implementation Photo

- (NSURL *)getThumbnailUrlWithSize:(enum kMyBikePhotoSize)photoSize
{
    NSString *path = [NSString stringWithFormat:kMyBikeApiPhotoDetail, @"bike_thumbnail", self.id];
    return [self urlWithPath:path photoSize:photoSize];
}

@end
```

----

というわけで私的メモ的なCoreData, MagicalRecord, mogeneratorの導入話はおしまいです。何か得ることがあったらまたアップデートすると思います。