MagicalRecordのREADMEを意訳

MagicalRecordのREADMEの抜粋を意訳しつつメモ。
原文のREADMEはこちら


※20140930 MR_contextForCurrentThreadとMR_SHORTHANDが3.0で廃止される事を追記

逐次githubのissuesをご確認下さい。



目次

インストール方法

参考:MagicalRecord2.0を使えるように準備する

  1. XcodeプロジェクトにMagicalRecordフォルダをドラッグしてね。
  2. CoreData+MagicalRecord.hをpchファイルかAppDelegateに追加してね
  3. MagicalRecordのメソッドを「MR_findAll」でなくて「findAll」みたく「MR_」なしで使いたい時は#define MR_SHORTHANDをpchファイルに追加してね。*1
  4. コードを書き始めよう!


必要条件

iOS4互換バージョンが使用可能。参照:tag 1.8.3


いつ保存すれば良いか

もし保存に長時間かかっていた場合、検討すべき点がいくつかあります。

  1. バックグラウンドスレッドでの保存

MagicalRecordではエンティティの変更から保存をバックグラウンドスレッドで処理する為、
シンプルでクリーンなAPIを提供しています、例えば以下のように記述できます。

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // 引数localContextインスタンスに対して保存する変更を行う
    //  ブロック内の処理はバックグラウンドスレッドで発生します

} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];
  1. 小さい保存処理になるようタスクを細分化します

大量のデータのインポートのようなタスクは小さなチャンクに分割します。
どの場合でもどんなデータ量でもフィットするタスクのサイズはないので(?訳が違うかもです..)
AppleのInstrumentsのようなツールでパフォーマンスを計測する必要があります。


長時間の保存のハンドリング

iOS
When an application terminates on iOS, it is given a small window of opportunity to tidy up and save any data to disk. If you know that a save operation is likely to take a while, the best approach is to request an extension to your application's expiration, like so:

iOSアプリの終了時はデータを整理し保存する為のわずかな機会が与えられます。
もし保存処理がしばらくかかる事が分かっている場合、良いアプローチは以下のようにアプリケーションの存命期間の延長を要求することです。

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // Do your work to be saved here

} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

事前に必ずbeginBackgroundTaskWithExpirationHandlerについてのドキュメントを読んで下さい、
不適切、又は、不要なアプリケーションの生存期間の延長はリジェクトされる可能性があります。


保存処理の変更

保存の為のAPIは、より一貫的な動作をするように改訂されており、
CoreDataフレームワーク命名パターンに沿ったものとなりました。
広範な作業は将来のアップデートを通じて、
saveメソッド(新規および非推奨の両方)が期待通り動作するよう自動化されたテストを追加することでした。


MR_saveは、今の所はカレントスレッドで同期的に実行されているものを元の状態に復元し、永続ストアに保存しています。
ただし、__ MR_saveメソッドは非推奨としてマークされ、MagicalRecordの次のメジャーリリース(バージョン3.0)_で削除される予定です。
ライブラリの将来のバージョンで同じ動作をしたい場合は、
MR_saveToPersistentStoreAndWaitを使用する必要があります。


新しいメソッド

以下のメソッドが新しく追加されました。

NSManagedObjectContext+MagicalSaves

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



MagicalRecord+Actions

  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
  • + (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveUsingCurrentThreadContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
  • + (void) saveUsingCurrentThreadContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block;



廃止予定のメソッド

以下のメソッドは新しい代替手段が支持された事により、非推奨にされています。
これらのメソッドはMagicalRecord 3.0で廃止されます。

NSManagedObjectContext+MagicalSaves

  • - (void) MR_save;
  • - (void) MR_saveWithErrorCallback:(void(^)(NSError *error))errorCallback;
  • - (void) MR_saveInBackgroundCompletion:(void (^)(void))completion;
  • - (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback;
  • - (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;
  • - (void) MR_saveNestedContexts;
  • - (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback;
  • - (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

MagicalRecord+Actions

  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion;
  • + (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(v

目次に戻る

ARC Support

MagicalRecordはARCを完全にサポートしているので何も構成する必要はありません。
手動メモリ管理をサポートする最後のバージョンは1.8.3であり、ダウンロードページから、
またはソースの1.8.3タグに切り替えることで利用可能です。

目次に戻る

■ 注釈

Third Party Blog Entries..(全ブログ英語)


ログ出力

MagicalRecordは全てのフェッチ要求とCoreDataの処理をロギングします。
データのフェッチ、保存時にエラーが発生した場合、これらのエラーはMagicalRecordによってキャプチャされます。デフォルトでログはNSLogを使用しています。
CocoaLumberjackをインストールしている場合は、MagicalRecordはCocoaLumberjackを使用し適切な出力にログを送信する設定です。
MagicalRecord内のすべてのログは、CoreData+ MagicalRecord.hのインポートの前に、
プリプロセッサ文を定義することで無効にできます。

#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0

目次に戻る

■ 使用方法

CoreDataスタックの設定

まず、プロジェクトのpchファイルにhファイルCoreData+ MagicalRecord.hをインポートします。
これで全ての必要なヘッダーをグローバルにインクルードできるようになります。
次にapplicationDidFinishLaunchingメソッド、またはawakeFromNib内に、
MagicalRecordクラスと以下のsetupメソッドのいずれかを使用します。

+ (void) setupCoreDataStack;
+ (void) setupAutoMigratingDefaultCoreDataStack;
+ (void) setupCoreDataStackWithInMemoryStore;
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void) setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;

上記の呼び出しは、Core Dataスタックの構成部品をインスタンス化しgetterとsetterを提供します。
インスタンスはMagicalRecordのwell-known-instance(既知のインスタンス)になり、
"デフォルト値"として認識されます。
(MR_defaultManagedObjectModelやMR_defaultContext等でアクセス可能になる。)


DEBUGフラグが設定されたデフォルトのsqliteのデータ·ストアを使用する場合、
新しいモデルバージョンを作成せずに、モデルを変更した場合、MagicalRecordでは、
古いストアを削除し自動的に新しいものを作成します。
変更を行うたびにアンインストール/再インストールをする事はもうありません!


そしてアプリケーションを終了する前にclean-upメソッドを使用することができます。

[MagicalRecord cleanUp];

目次に戻る


iCloudサポート

iOS5以降とOSXのLion10.7.2以降用に構築されたアプリは、
CoreDataストアを同期するiCloudを活用することができます。
MagicalRecordでこの機能を実装するには、前のセクションに記載されている呼び出しの代わりに、
以下のsetupメソッドを呼び出します。

+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)icloudBucket localStoreNamed:(NSString *)localStore;
+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent;
+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent completion:(void(^)(void))completion;



iCloudの詳細とアプリケーションがiCloudに適合していることの保証についてはApple's iCloud Notesを参照してください。
(参考:iCloud設計ガイド)

特に注目すべきは最初のヘルパーメソッドで、
アプリケーションのバンドルIDに基いて、自動的にNSPersistentStoreUbiquitousContentNameKeyを生成します。

+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)icloudBucket localStoreNamed:(NSString *)localStore;

複数の異なるiCloudストアを管理するような状況でcontentNameKeyを指定する場合、
他のヘルパーメソッドを使用することを推奨します。
目次に戻る

デフォルトの管理オブジェクトコンテキスト

Core Dataを使用するときは、NSManagedObjectとNSManagedObjectContextの2つのオブジェクトを扱います。
MagicalRecordはアプリ内で使用するデフォルトNSManagedObjectContextの為に単一の場所を提供します。
これは、シングルスレッドのアプリケーションには素晴らしい事です。
あなたは簡単な呼び出しで、このデフォルト·コンテキストを取得することができます。

[NSManagedObjectContext MR_defaultContext];

inContextメソッド(後述)をオーバーロードして、
特定のコンテキストに検索やフェッチ要求を行っていない場合にデフォルトコンテキストが使用されます。
デフォルト永続ストアを元にして、別スレッドの新しい管理オブジェクトコンテキストが必要な場合、
以下のメソッドを利用します。

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];

このメソッドは同一のオブジェクトモデルと永続ストアを使用していますが、
メインスレッド以外のスレッドで使用する為の完全に新しいコンテキストを作成します。


ここで新しく生成した「myNewContext」をメインスレッドの、
全フェッチ要求にとってのデフォルトコンテキストに設定したい場合は以下の様にします。

[NSManagedObjectContext MR_setDefaultContext:myNewContext];



MagicalRecordは又、複数のスレッド内の管理オブジェクトコンテキストを保持するヘルパーメソッドを持っています。
これを利用すれば、呼び出し元のスレッドに関係なく正しいNSManagedObjectContextインスタンスにアクセスできすることができます。このメソッドは以下の通りです。*2

[NSManagedObjectContext MR_contextForCurrentThread];

※デフォルトコンテキストはメインスレッドを利用して生成、設定する事を推奨します。
目次に戻る


Fetching(フェッチ)

基本的なフェッチ
MagicalRecordの殆どのメソッドは、結果をNSArrayで返します。
この事から、部署エンティティと関連をもったPersonというエンティティを持っていた場合(AppleのCoreDataのドキュメントで見られるような)、全てのPersonエンティティを永続ストアから取得するには以下の様に記述します。

//このコードを動かすにはpchファイルに"#define MR_SHORTHAND"を定義する必要があります。
NSArray *people = [Person findAll];

// もしくは名前空間を利用した長いバージョンで動作します。
NSArray *people = [Person MR_findAll];

プロパティでソートされた結果を取得する場合

NSArray *peopleSorted = [Person MR_findAllSortedByProperty:@"LastName" ascending:YES];

複数のプロパティでソートされた結果を取得する場合

NSArray *peopleSorted = [Person MR_findAllSortedByProperty:@"LastName,FirstName" ascending:YES];

もしオブジェクトをデータストアから取得する一意な方法がある場合、
直接そのオブジェクトを取得する事もできます。

Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];



Advanced Finding(高度なフェッチ)
検索をより具体的に行いたい場合、述語(NSPredicateオブジェクト)を指定する事ができます。

NSArray *departments = [NSArray arrayWithObjects:dept1, dept2, ..., nil];
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];

Returning an NSFetchRequest(フェッチ要求の返却)

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];

上記の1行ずつ、それぞれについて、
NSFetchRequest、NSSortDescriptorsとシンプルなデフォルトのエラー処理スキーム(コンソールへのロギングを除く)のフルスタックが作成されます。


フェッチ要求のカスタマイズ

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter];
[peopleRequest setReturnsDistinctResults:NO];
[peopleRequest setReturnPropertiesNamed:[NSArray arrayWithObjects:@"FirstName", @"LastName", nil]];
...
NSArray *people = [Person MR_executeFetchRequest:peopleRequest];

目次に戻る


エンティティ数の検索
永続ストア内のエンティティのカウント行う事もできます。
この処理は永続ストア上で実行されます。

NSNumber *count = [Person MR_numberOfEntities];

もし述語(NSPredicate)かその他のフィルタを元にしたエンティティのカウントを取得したい場合:

NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

以下のメソッドはNSNumberではなく、NSUIntegerの返却に対応しています。
(通常の呼び出しにはMR_が必要な事に注意)

  • countOfEntities
  • countOfEntitiesWithContext:(NSManagedObjectContext *)
  • countOfEntitiesWithPredicate:(NSPredicate *)
  • countOfEntitiesWithPredicate:(NSPredicate *) inContext:(NSManagedObjectContext *)



集計操作

NSPredicate *prediate = [NSPredicate predicateWithFormat:@"diaryEntry.date == %@", today];
int totalFat = [[CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"fatColories" withPredicate:predicate] intValue];
int fattest  = [[CTFoodDiaryEntry MR_aggregateOperation:@"max:" onAttribute:@"fatColories" withPredicate:predicate] intValue];



異なるコンテキストからの検索
全ての検索、フェッチメソッドは以下の様にinContextパラメータを持っています。
All find, fetch and request methods have an inContext: method parameter

NSManagedObjectContext *someOtherContext = ...;

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];
...

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"Gump" inContext:someOtherContext];
...

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

目次に戻る


新しいエンティティの生成

エンティティの新しいインスタンスを作る必要が場合、以下のメソッドを利用するか、

Person *myNewPersonInstance = [Person MR_createEntity];

コンテキストを指定します。

NSManagedObjectContext *otherContext = ...;

Person *myPerson = [Person MR_createInContext:otherContext];

目次に戻る


エンティティの削除

1つのエンティティの削除

Person *p = ...;
[p  MR_deleteEntity];

コンテキストを指定した場合

NSManagedObjectContext *otherContext = ...;
Person *deleteMe = ...;

[deleteMe MR_deleteInContext:otherContext];

CoreDataには全てのエンティティの一括削除やトランケート操作がありません。
MagicalRecordでは一括削除の方法の1つとして以下のメソッドを提供しています。

[Person MR_truncateAll];

コンテキストを指定する場合は以下の様に記述します。

NSManagedObjectContext *otherContext = ...;
[Person MR_truncateAllInContext:otherContext];

目次に戻る


スレッド上でCoreDataの操作を実行する

MagicalRecordはスレッド処理用のバックグラウンドコンテキストを設定する、いくつかの便利なメソッドを提供します。

バックグラウンドのセーブ操作は少し違いはありますが、UIViewのアニメーションブロックメソッドにインスパイアを受けています。

  • データ保存コードを追加したblockがメインスレッド上で実行される事はありません。
  • 1つのNSManagedObjectContextが操作の為に提供されます。

例えば、PersonエンティティにfirstNameとlastNameフィールドをセットしたいという時、
バックグラウンドコンテキストをセットアップするにはMagicalRecordを以下のように利用します:

Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

    Person *localPerson = [person MR_inContext:localContext];

    localPerson.firstName = @"John";
    localPerson.lastName = @"Appleseed";
}];

このメソッドでは、指定されたブロックが処理実行の為に適切なコンテキストを提供します。
バックグラウンド処理の終了をデフォルトコンテキストに伝えるよう、セットアップする必要はありませんが、
変更が別のスレッドで実行されたので更新をする必要があります。

このセーブブロックが完了した後に、アクションを実行するするには、
completion block(完了ブロック)に記述します。

Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

    Person *localPerson = [person MR_inContext:localContext];

    localPerson.firstName = @"John";
    localPerson.lastName = @"Appleseed";

} completion:^(BOOL success, NSError *error) {

    self.everyoneInTheDepartment = [Person findAll];

}];

この完了ブロックはメインスレッド(キュー)で呼び出されるので、
UI更新のトリガとしても安全です。
目次に戻る

*1:※MR_SHORTHANDはバージョン3.0で廃止されるようなので使わない方が良さそうです。http://stackoverflow.com/questions/21985859/mr-shorthand-does-not-work

*2:MR_contextForCurrentThreadは2.3.0で非推奨にマークされ3.0で削除予定のようです。https://github.com/magicalpanda/MagicalRecord/blob/develop/Docs/Saving.md