MagicalRecordを使う


今まであまりCoreData周りのライブラリ使ったことなかったのですが、今回MagicalRecordを利用することになりました。
ライブラリの機能が豊富でドキュメントを見てもわからず、ちょっとずつ異なるメソッド名を見てもどういうときに使うのかわからなかったので調べてみました

## 前提

### NSManagedObjectContext(オブジェクトの管理スペース)のContextType

|Method|説明|
|------|----|
|`NSConfinementConcurrencyType`|スレッド拘束パターン、 コンストラクタにタイプを指定しないで生成した場合のデフォルト|
|`NSPrivateQueueConcurrencyType`|private dispatch queueを使用する。バッチ処理や永続化処理に向く|
|`NSMainQueueConcurrencyType`|Main Queueを使用する。Viewの描画が固まってしまうので、大量のデータをこのContextで処理するのは避けるべき|

基本的に中規模以上のアプリは、NSManagedObjectContextを複数生成して使用します。
MagicalRecordを使うと、NSManagedObjectContextの生成とPublish/Subscribe(NSNotificationCenterなど)の登録、NSManagedObjectContextへの保存、永続化処理を簡単に行えるようです。

### MagicalRecordのsetupメソッドで生成されるNSManagedObjectContext

|Context|説明|
|-------|----|
|`MR_rootSavingContext`|<ul><li>persistentStoreCoordinatorが接続されている</li><li>`NSPrivateQueueConcurrencyType`</li></ul>|
|`MR_defaultContext`|<ul><li>ParentContextとして、`MR_rootSavingContext`を設定</li><li>`NSMainQueueConcurrencyType`</li></ul>

ざっくりとした理解だと、MR_rootSavingContextの方はデータの永続化、MR_defaultContextはデータの表示・更新などで利用するような感じです。

## 保存メソッド

最初に混乱するのが、`NSManagedObjectContext (MagicalSaves)`と`MagicalRecord (Actions)`の両方に保存メソッドがあることです。

* `NSManagedObjectContext (MagicalSaves)`に定義されているメソッドは、`NSManagedObjectContext`への保存または永続化処理
* `MagicalRecord (Actions)`に定義されているメソッドは、`NSManagedObjectContext`を新たに生成または取得して`NSManagedObjectContext (MagicalSaves)`に定義されているメソッドを呼び出している

### `MagicalRecord (Actions)`

やっていることは下のような処理です。

1. contextを生成
2. 1.で生成したものをBlocksで渡して、アプリ側でそのContextを使ってデータ作成・更新処理などが行えるコールバック呼び出し
3. `NSManagedObjectContext (MagicalSaves)`に定義されているメソッドを呼び出し、データの永続化まで行う

contextの生成・取得はメソッドが自動でやってくれるため、アプリ側ではそのcontextを使ってコールバックに必要なデータの作成・更新の処理を書くだけです。

#### 非同期呼び出し処理タイプ

|Method|説明|
|------|----|
|`+ (void)saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;`|下のメソッドをcompletionBlocksがnilで呼び出し|
|`+ (void)saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;`|親のcontextとしてMR_rootSavingContextを指定して、NSPrivateQueueConcurrencyTypeのcontextを作成します|
|`+ (void)saveUsingCurrentThreadContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;`|<table>Main threadから呼び出したときとそれ以外で作成されるcontextが異なります。ただ、MR_defaultContextを経由するのは変わりません。
* Main threadから呼び出した時
    * localContext: MR_defaultContextを利用

* Main thread以外から呼び出した時
    * localContext: Thread毎に作成/登録したcontextを利用(再利用してる?)
    * parentContext: MR_defaultContext(NSMainQueueConcurrencyType)
</table>|

#### 実行完了待ちタイプ

上の実行完了待ちバージョン

* `+ (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;`
* `+ (void) saveUsingCurrentThreadContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block;`

### `NSManagedObjectContext (MagicalSaves)`

`MagicalRecord (Actions)`に定義されたメソッドを利用すると、contextの生成からデータの永続化まで自動で行ってくれますが、それでは困ることがあります。

1. ローカルの登録フォームからデータを生成したが、APIでサーバーにリクエストを送ったらエラーが返ってきた。
2. 画面を閉じる前に保存したい(保存タイミングの制御)
3. 複数のAPIからデータを取得して、全部揃ったタイミングで保存

1.の場合は、サーバーに保存できるまでCoreDataで管理するデータを生成しないという選択肢がありますが、スコープを超えてプリミティブなデータを持っておくのはダルいです。
それよりも、専用のcontextを生成して、サーバーに保存が成功したらローカルに保存・永続化、保存失敗したらcontextごとなかったことにするとかの方が簡単です。

あと、一回一回いちいち永続化するのではなく、一連の処理が完了したら永続化したいところです。


定義されている保存メソッドは下記の通りです。

* `- (void) MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion;`
* `- (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion;`
* `- (void) MR_saveOnlySelfAndWait;`
* `- (void) MR_saveToPersistentStoreAndWait;`
* `- (void) MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion;`

メソッド名に`Wait`が入っている場合は、実行完了まで待つタイプで、`ToPersistentStore`が入っているのは、ParentContextをさかのぼって保存するタイプです。さかのぼったcontextにpersistentStoreが接続されていれば永続化処理が行われます。
あと、`saveOnlySelf`が入っているものは、そのcontext自身にのみ保存するタイプです。

上のメソッドは、自分自身のみかルートとなるcontextまでさかのぼって保存するしかないため、
もっと細かく保存するcontextやタイミングを指定したい場合は、MagicalRecordに頼らずに、CoreDataフレームワークで用意されているメソッドを使うしかないです。

ということで、何を調べていたかというと既存の処理が重いのでどのメソッドを実行しているときに重いのかを調査したかっただけです。