CoreData3分クッキング

2007/03/30 (Fri) 22:07:46 JST

今日のお料理は、マカーなら誰でも簡単おいしく使えるCore Dataです。Core Dataは使う分にはとっても簡単ですが、理解するには複雑なフレームワークです。そこで今日は、はじめからCore Dataアプリケーションを作ろうとせずに、Core Dataを知ることからはじめましょう。

材料はこちら。

今日のテキストは CoreDataCooking.zip です。

簡単おいしいCore Data

Core Dataを一言で言うと、オンメモリ・データベースです。大量のデータの読み書き、管理をするのが目的です。「大量のデータなんか使わないよ」というそこのあなたでも、一通り知っておくときっと幸せが訪れます。

データべースと聞くと難しそうで身構えてしまう方もいるかと思いますが、たかだか二つ三つのクラスを覚えれば使えるようになりますので、次のOSがリリースされるまでに一度くらいは試してみましょう。

基本的な考え方

エンティティ - リレーションシップ

Core Dataでは、「エンティティ - リレーションシップ」という考え方でデータを作っていきます。簡単に言いますと、エンティティはメソッドのないクラス定義のようなもので、リレーションシップはエンティティ間の対応関係です。余計わかりませんね。

開発ツールやモデル図を見てるうちにわかってくると思いますので、ここでは「インターフェースは?AppKitの、データはCore Dataの考え方がある」程度におさえておいてください。

エンティティ・リレーションシップはデータモデルファイルで定義します。これは拡張子が ".xcdatamodel" のファイルで、新規ファイルで「設計 / データモデル」を選ぶと用意することができます。ひまがあったらCore Dataのサンプルを開いてみましょう。

Core Dataのコアデータ

Core Dataの中心となるデータは、もちろんデータべースから取得したデータです。このデータはどんなオブジェクトでもOKとはいかず、NSManagedObjectオブジェクトでなければいけません。

好奇心の強い人向けに: カスタムクラス

NSManagedObjectのデータとしての機能は、NSDictionaryとあまり変わりません。最初のうちはこれでも十分ですが、データの利用範囲が広がるうちに関連するメソッドも増えてきて、オブジェクト指向らしくデータとメソッドをまとめたくなってくると思います。そのときはNSManagedObjectのサブクラスを定義して使います。

主に使うクラス

Core Dataを使うために最低限必要なクラスは、

くらいです。次の図を見てください。

NSManagedObjectが引っ張ってきたデータ、?NSFetchRequestが取得したいデータの条件を指定するクラスです。もう一つ名前からして何をするのかわからない?NSManagedObjectContextというクラスがありますが、実はCore Dataのうち最も重要なクラスで、これがメモリ上のデータべースです。

基本的な使い方

それではさっそく使ってみましょう。Core Dataも人の子、使い道は単純です。データを取得して編集する、基本はこれです。

アプリケーションの準備

まずはXcodeを起動して新しくプロジェクトを作ります。リストからCore Dataアプリケーションを選択し、"CoreDataCooking" と名前をつけて作成します。Core Dataアプリケーションを作成すると、いくらかCore Data用のテンプレートも一緒についてきます。

インターフェース

Cocoaバインディングを使うとかなりの部分をコーディングなしで済ませることができますが、それだと意味がないので、ひねりもへったくれもないこんな感じのインターフェースで進めます (実はもっと複雑なインターフェースを予定していたのですが、Cocoaバインディングがよくわからなくてあきらめたことは秘密です) 。

今回は生成されたデリゲートクラスにアクションを付け足して使います。すでに saveAction: が定義されていますが、さらに次のアクションを追加してください。

アクションを追加したら、上の画像のようにテキストフィールドと各ボタンを配置します。

テキストフィールド
value バインディングに predicateString をバインド
Fetch ボタン
target アウトレットに fetchAction: を接続
Save ボタン
target アウトレットに saveAction: を接続
Reset ボタン
target アウトレットに resetAction: を接続
Insert ボタン
target アウトレットに insertAction: を接続
Delete ボタン
target アウトレットに deleteAction: を接続
Update ボタン
target アウトレットに updateAction: を接続

モデル

今回は調理済みのモデルを使います。次のようなモデルです。

図で見るとなんとなくわかるかと思います。図の各表がエンティティで、クラスの毛色の変わったもの、ととらえてもいいでしょう。図中の「属性」は、機能的にはインスタンス変数にあたります。もう一つの「関係」は、エンティティ同士が線でつながれています。ProductエンティティのproductTypeは?ProductTypeエンティティと、CompanyエンティティのproductsとProductエンティティのcompanyは相互につながっています。

関係がどういう働きをするのか、言葉で理解するよりもコードとオブジェクトを見るほうが理解しやすいと思いますから、図だけ覚えておいて先に進みましょう。

下ごしらえ

Core Dataを使うには、次の準備が必要です。

しかし、準備はすべてXcodeがやってくれます。Core Dataアプリケーションを作ると、"...Delegate" クラスも一緒に作られます。よくわからないようなメソッドが並んでいますが、もうここに準備のためのコードが書かれています。次のメソッドだけおさえておきましょう。

オブジェクトをフェッチする

それでは、まずオブジェクトをデータべースから引っ張ってきましょう。これをフェッチと呼びます。オブジェクトを変更するにも削除するにも、まずオブジェクトがないと始まりません。

オブジェクトをフェッチする手順はこうなります。

各手順を確認する前にコードを見てみましょうか。

リスト: fetchAction:

// predicateStringはテキストフィールドで設定する
pred = [NSPredicate predicateWithFormat:predicateString];

// エンティティ
entity = [NSEntityDescription entityForName:@"Product"
            inManagedObjectContext:[self managedObjectContext]];

// 要求に上記をセット
request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
[request setPredicate:pred];

// フェッチ
fetchedObjects = [[self managedObjectContext] 
                executeFetchRequest:request error:nil];

NSPredicateがはじめて出てきました。これが条件を表すオブジェクトで、"name = '?NewProduct'" のような条件式を書きます。条件式は文字列なので、テキストフィールドで入力した値をそのまま使えます。この条件に沿ったオブジェクトが最終的に返ってくるわけです。

ただ、フェッチのたびに一連のコードを書くのは結構面倒ですね。サンプルコードには、おまけとしてもう少し簡単にフェッチできるメソッドを追加しておきましたので、のぞいてみてください。

好奇心の強い人向けに: フェッチしたオブジェクトの行方

フェッチしたオブジェクトは、フェッチに使った?NSManagedObjectContextが保管しています。オブジェクトは?NSManagedObjectContextに管理されるので (だから Managed Object ) 、勝手に release してはいけません。

好奇心の強い人向けに: 既存のオブジェクトの削除と新しいオブジェクトの追加

フェッチしたオブジェクトは変更するほか、そのオブジェクトを削除するのにも使います。オブジェクトを削除するときは、削除したいオブジェクトを引数にして、?NSManagedObjectContextに deleteObject: メッセージを送ります。

新しいオブジェクトを作るには、?NSEntityDescriptionに insertNewObjectForEntityForName:inManagedObjectContext: メッセージを送ります。このメソッドは新しいオブジェクトを生成し、指定した?NSManagedObjectContextに登録してから返します。

追加・削除のいずれも?NSManagedObjectContextに save: メッセージを送るまではデータべースに反映されないことに注意してください。

オブジェクトを変更する

ではメインの処理に移りましょう。フェッチしたオブジェクトはNSManagedObjectのインスタンスで、 これはNSDictionaryのようなものです。Productエンティティのオブジェクトの中身をNSDictionary風に表すとこのようになります。

<NSManagedObject> = {
  name = "NewProduct"
  productType = <NSManagedObject> = {
                   name = "Software"
                }
  compary = <NSManagedObject> = {
               name = "Apple"
            }
}

大雑把に分類すると、

となります。ちなみにCore Dataの説明でよく出てくる「プロパティ」とは、属性と関係をすべてひっくるめた言い方です。

データモデルに登場した「属性」と「関係」も、オブジェクトの中身を見れば一目瞭然です。もう一度モデル図を見て、オブジェクトの中身と照らし合わせてみましょう。

さてメインのオブジェクトの変更ですが、こちらは御存知の?KeyValueCodingを使うだけです。valueForKey: でオブジェクトにアクセスし、setValue:forKey: でオブジェクトを変更します。?KeyValueCoding大活躍です。

リスト: updateAction:

[obj setValue:[[NSDate date] description] forKey:@"name"];

ただし、オブジェクトを変更してもすぐにはデータべースに反映されません。保存しない限り、ここで行った変更は一時的なものに過ぎませんので注意してください。

好奇心の強い人向けに: 変更したオブジェクトの監視

オブジェクトは自分が変更されたかどうかを知っています。オブジェクトは変更されると、自分を管理している?NSManagedObjectContextに変更を知らせます。変更のあったオブジェクトは更新待ちとなり、updatedObjects に分類されます。

オブジェクトを保存する

では、いま変更したオブジェクトを保存しましょう。保存は簡単で、?NSManagedObjectContextに save: メッセージを送るだけです。Core Dataアプリケーション時に生成されるデリゲートクラスには、あらかじめデータを保存するアクションが定義されています。

リスト: saveAction:

if (![[self managedObjectContext] save:&error]) {
    [[NSApplication sharedApplication] presentError:error];
}

ここではエラー処理を行っていますが、メッセージがないとか渡すオブジェクトを間違えたとかの例外が発生するのではなく、オブジェクトの内容を検証の結果がエラーとして発生します。例えば必ず設定しなければならない属性が nil だとか、データモデルで設定したものと異なるデータ型のオブジェクトが設定された、などです。エラーがなければ無事データが保存ことになります。

saveAction: が定義されているように、Core DataとInterface Builder、Cocoaバインディングは連携できます。今回は自前でコードを書きましたが、Cocoaバインディングを活用するとほとんどコードを書かずにCore Dataを使うことができるようです。詳しくは付属サンプルの?EventManagerを見てみるといいでしょう。

好奇心の強い人向けに: 保存後の?NSManagedObjectContext

データを保存すると、?NSManagedObjectContextの変更待ちオブジェクト (insertedObjects, deletedObjects, updatedObjects)がすべて空になります。つまり、?NSManagedObjectContextはオブジェクト (?NSManageObject) をすべてため込み、どのような状態にあるか管理し、データ操作のインターフェース管となるのです。?NSManagedObjectContextこそ、Core Dataユーザから見た場合のデータべースになります。

ワンポイントアドバイス

Core DataはべースとなったWebObjectsのEOF (Enterprise Objects Framework) よりもコンパクトですが、全容を把握するのにはちょっと手間がかかります。さらにそれよりも?AppKit, Cocoaバインディングとどう組み合わせて使うかのほうがややこしいので、下手にInterface Builderで試していくよりCore Dataのコードを書いてみるといいでしょう。

私はCore DataよりもCocoaバインディングで連日挫折中です。


Inverse Pages: CoreData