Study CoreData 3 ~デルモに手を出すなら慎重に…~

大変な事に、この『Study CoreData』もすでに4エントリ目になりました。
しかも、まだ独自のデーターベース作成に一歩も入っていない始末…。

ですが、まず理解する事でこれからの作業が分かりやすくなる(はず?)です!!

ってことで、引き続きデフォルトで作成されたプロジェクトを検証していきます。

前回までのエントリでコードとして書かれている核の部分はある程度理解できたと思うことにして^^;
今回は、
どのように<NSManagedObjectModel>(データの設計図)が作られるのか
を見ていくことにします。

それでは、今まで見てきた『TodoCoreプロジェクト』を開きましょう。

注意:投稿者自身もCoraDataについて勉強中のため、このシリーズには誤りが含まれている可能性があります。もし、間違いに気付かれた方はコメント欄もしくはtwitterなどでご指摘いただけると幸いです<(_ _)>
また、開発環境はXcode3.2.3 iPhone SDK 4です。実機でのテストなどは自己責任でお願いいたします。

 
 
1. データを設計するデータモデリングツール

まず、<グループとファイル>ペイン<Resouces>グループにある<TodoCore.xcdatamodeld>を見てみます。

今以下のように表示されているのが、データモデリングツールと呼ばれるものです。

CoreDataのモデリング、つまりデータの設計は、主にこのViewの中で行われます。

分かりやすくするために、

左上のペインを<エンティティビュー>、
上部真ん中のペインを<プロパティビュー>、
右上のペインを<属性ビュー>、
そして方眼状になっているビューを<モデリングビュー>

と呼ぶことにしましょう。
 
 
見て分かる通り、この4つのビューはすべて同じモデルの情報を示しています。
そして、モデリングビューに青い角丸で囲まれた物が<エンティティ>と呼ばれるモデルになっています。

もう少し説明すると<エンティティ>とは、
単一のデータの単位であり、
その中にAttribute(属性)Relationship(他のエンティティとの関連)を持っています。

つまり、単一のデータの設計図になる物です。
 
 
2. エンティティとNSManagedObjectModelの違い

以前に、

<NSManagedObjectModel>とは、データの設計図のようなもの。

と書きましたが、

<エンティティ>が単一のデータの設計図であるのに対して、<NSManagedObjectModel>は複数の<エンティティ>の集まりやつながりを設計したもの。


と考えると良いのではないかと思います。
(このサンプルではエンティティがひとつしか無いので分かりにくいですが。)

もっと噛み砕いて説明すると、

例えば「お絵描きセット」に含まれている、
色という属性を持った絵の具や、長さや太さなどの属性を持った筆大きさや形という属性を持ったパレット…etc.
これらはすべて個々の<エンティティ>であるのに対して、
それらを入れておくための『絵の具セットの箱』はNSManagedObjectModel

という事になります。

このモデリングビューで言うと、
<エンティティ>は角丸の中のもので、<NSManagedObjectModel>はこの方眼上のすべて。という事です
 
 
3. モデリングされたデータと実装ファイルのコード

実はこの<Eventエンティティ>は、『Study CoreData 1,2』で登場した”良いヤツ”フェッチリクエストに渡されていた名前(entityForName:)と同じになっています。

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSEntityDescription *entity =
 [NSEntityDescription entityForName:@"Event" 
      inManagedObjectContext:self.managedObjectContext];

[fetchRequest setEntity:entity];

さらに、そのEventエンティティの中の<Attribute>にある<timeStamp>も
“並び替えの達人”<NSSortDescriptor>に渡されたKeyと同じです。

NSSortDescriptor *sortDescriptor = 
 [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];

ですが、実はこのエンティティの中には実際のデータ(生データ)は何も入っていません
つまり空っぽの状態ですが、エンティティは『そこにどんなデータが入るのか』を決めているだけのものなので、実際のデータはアプリを操作していく中でユーザーによって決定されていくものになります。
(そして、エンティティの情報を元にデータをオブジェクト化したものが<NSManagedObject>になります。)
 
 
コードを見ていく中ではまだ触れていませんでしたが、このプロジェクトのデフォルトの状態で+ボタンをタップする度にひとつの<Eventエンティティ>(のデータオブジェクト)が作られて、その属性である<timeStamp>(つまりタップした時間)が記憶され、テーブルビューに新しい順にソートされ表示される…という仕組みになっています。
 
 
…とここまでは何となく分かったのですが、『追加ボタンを押したら押した時間が表示される』機能だけではまともなアプリは作れませんよね。

ってコトで、実際にTodoアプリを作るためのデータを設計していってみるとしましょうか。
 
 
4. 試しに作ってみる。

と、ちょっと待ってくださいっ!!

実は他の.hや.mのファイルと違って、このままこのファイル(TodoCore.xcdatamodeld)に手を加えると、アプリが起動しなくなってしまうんです!

実はシミュレータや実機のアプリを一旦削除した上で再ビルドすれば起動するのですが、それは面倒ですし、もしリリースした後に変更した場合はすべてのユーザーに一旦アプリを削除してもらわないといけなくなってしまいます。
クラウドなどとの同期機能を採用していなければ、それまでユーザーが蓄積したデータも消えてしまいかねません。

それではあまりにもマズいので
このファイルには手を加えずに、データモデルを変更する方法を覚えておきましょう。
 

それが、以下の方法です。

<TodoCore.xcdatamodeld>を選択した状態で、メニューバーの設計から<データモデル><モデルバージョンを追加>を選択します。
つまり、データモデルをバージョンアップさせる方法です。

すると、このように新しいファイルが作られ、古いモデルバージョンには2という数字が付けられます。

ここで注意しておく事は数字が付いているファイルは、数が少ないもの順に古いファイルになる事です。
たとえば、もう一度モデルバージョンを追加すると<TodoCore 3>というファイルが作られますが、それは2よりも新しいファイルになります。
そして、緑色のバッジが付いているファイルが常に現在使用しているファイルとなります。
(<データモデル><現在の設定>を選択すると古いファイルに戻す事も出来ます)

まぁ、とりあえずこれで安心してデータモデルをいじれる状態になりました。
 
 
5. 今度こそモデリングしてみる^^;

まず、現在ある<Eventエンティティ>をどこか端の方にでも、追いやっておいてください。
(捨てないでっ!!)

 
まず、新規エンティティを追加します。
追加するには、エンティティビューの下にある+ボタンをクリックするか、モデリングビュー上で右クリックして<新規エンティティを追加>を選びます。
(この辺りの操作の説明は、何となく分かると思うので端折っていきます)

そして、そのエンティティに<Task>という名前を付けておきます。

次にこの<Task>に必要な属性を5つ加えてみます。

1. title (Todoのタイトル)
2. priority (重要度:0〜3)
3. completed (完了したかどうか)
4. dueDate (締め切り日)
5. created (Taskを作成した日時)

 
すると、以下のようになると思います。

なんだかちょっとTodoアプリっぽくなってきた気がしますね^^
 
 
ただ、このままではそれぞれの属性にどんな型のデータが入るのか分からないので、それぞれの属性のデータ型を指定していきます。
このデータ型を指定する作業は属性ビューでやるのが効率的だと思うのですが、データ型を決めた際に色々と他の詳細設定も出来るようになっています。

オプション:デフォルトではオンになっていますが、これはその属性が必須かどうかを指定します。(必須にする場合はオフにする)
その他に最大値/最小値の設定、デフォルトの値などが決められます。

一時:このチェックボックスをオンにするとその属性のデータは、アプリ動作時だけ使われて保存されないデータになります。

索引付き:これをオンにするとソートや検索をする際の処理を高速にできるようになります。(ただし、常に自動的にインデックスを付ける事になるため、作成時の処理などが遅くなる可能性もあるみたい。)

 

ちなみにここで設定できるデータ型は以下のようになります。

・整数16:16bitの整数(NSNumber)
・整数32:32bitの整数(NSNumber)
・整数64:64bitの整数(NSNumber)
・10進:38桁までの整数値(NSDecimalNumber)
・倍精度:duble(NSNumber)
・浮動小数点:float(NSNumber)
・文字列:(NSString)
・論理値:(NSNumber)
・日付:(NSDate)
・バイナリデータ:(NSData)
・変換可能:独自で変換する(UIImageなど)
・未定義:

 
で、今回は次のように設定してみました。
(特に明記していないものはデフォルトのままです)

1. title <データ型:文字列、オプション:OFF、最小長:1>
2. priority <データ型:整数16、オプション:OFF、最小長:0、最大長:3、デフォルト:0>
3. completed <データ型:論理値、オプション:OFF、デフォルト値:偽>
4. dueDate <データ型:日付、オプション:ON>
5. created <データ型:日付、オプション:OFF>

 
 
この設定の内容をざっと説明します。

1. titleは、必ず必要なものとして必須とし、最低一文字(?)は入力されなければならない。
2. priorityは0~3なので最小長と最大長を設定して型は整数15。オプションでもいいのだが、なにも入力されないときはデフォルトの0とする。
3. completedも同様に必須。デフォルトは偽(NO/false)
4. dueDateはオプション。
5. createdは自動生成させるため必須とした。

 
と、ここまでで、とりあえずの項目は出来ましたが、なんだか物足りない感じですね〜
やっぱりアプリとして作るには、これでは最低限の最低限すぎる感じなので、あと2つエンティティを加えてみます。
 
 
6. 複数のエンティティを関連づける

新たに加えるエンティティは<Tag><Category>です。
 
Tagエンティティは<Task>にタグを付けられるようにするため。
Categoryエンティティは、<Task>を大まかなカテゴリー分け(例えば、仕事のタスク、プライベートのタスクなど)が出来るようにするために使います。

そして、その両方に<name>という文字列型の属性を追加して、
<Task>の<title>属性と同じように、オプションをオフ、最小長を1にしておきます。
 
加えて<Category>の<name>属性のデフォルト値を”No Category”にしてみましょう。

そして、このままでは新たに加えた2つのエンティティは何の役にも立たないので、それぞれを<Task>に関連づける必要があります。
 
 
まず、それぞれのエンティティに関連を追加します。

関連の名前は以下のようにします。

<Task>エンティティ 関連:category, tags
<Tag>エンティティ 関連:tasks
<Category>エンティティ 関連:tasks

 
この関連名の中で<Task>のcategory以外は複数形になっているので注意してください。
(あと言い忘れましたが、基本的にエンティティ名は頭文字を大文字に、属性や関連などは小文字にするのが一般的なようです。クラス名とオブジェクト名みたいな感じですね。)

次にどういう関連付けをするかを考えてみます。

まず、<Task>と<Tag>の関係は、ひとつのTodoに対して複数のタグを付けられるようにしたいですね。
(これを対多関連(to-Many Relationships)と言います。)
 
またその逆に、あるタグをひとつのTodoにしか付けられないのではちょっと困りますよね。なので、こちらもひとつのタグに対して複数のTodoが関連づけられるようにしたいところです。
つまり双方に対多な関連付け(Many-to-Many Relationships)をする事になります。
 
 
そこで、まず<Tag>エンティティの関連<tasks>を選択して、
ディスティネーション(関連)で<Task>を選択
逆を<tags>
対多関連をオン
削除ルールを<無効にする>
にします。

すると以下のようになります。

<Task>と<Tag>が矢印で関連づけられてさらに<Tag>から<Task>に向かった矢印は二重になっていますね。
これで<Task>は複数の<Tag>を持てることが分かります。
(“逆”と”削除ルール”については後述します。)
 
 
そして、今度は<Task>から<Tag>への関連も設定するのですが、<Tag>からの設定が既に関連づけられた状態になっています。
ですので、対多関連のみをオンにすれば完了です。

これで双方に対多な関連付けができました!

お次ぎは<Category>と<Task>の関連付けですね。

ひとつの<Task>はひとつの<Category>しか持てないようにしようと思います。
これは、テーブルビューで表示する際にこの<Category>でセクション分けしたいからです。
ひとつの<Task>が複数の<Category>を持てるようにしてしまうと、表示させる際にドコのセクションに表示したら良いのか分からなくなってしまいます。
(先の設定で<Category>の<name>属性のデフォルト値を”No Category”としたのもこのためです。ユーザーがカテゴリを指定しなかった場合は、そのTaskは”No Category”というカテゴリに入れられます。少し変な言い回しですが^^;)

つまり、<Task>から<Category>への関連は単一になります。
逆に、当然ですがひとつの<Category>を複数の<Task>が使えるようにしなければならないのでCategoryからの関連は対多関連(One-to-Many Relationships)にします。
 
 
それでは、以下のように設定してみましょう。

<Category>エンティティの関連<tasks>
ディスティネーション:Task、逆:category、対多関連:オン、削除ルール:カスケード

<Task>エンティティの関連<category>
ディスティネーション: Category、逆:tasks、対多関連:オフ、削除ルール:無効にする

 
で、以下のようになっていればOKです。

ここまでできたところで、データモデリングはひとまず完成としておきましょう。

それと先程説明しなかった<関連>の”逆”と”削除ルール”について書いておきます。
 

この逆と削除ルールは共に
『自分自身が削除された時に相手側(関連先)のデータとの関連をどうするか?』
ということを指定しておくために設定する項目です。

 
つまり『逆』を設定すると自分が削除されたときにその逆へ『僕は削除されたよ』と通知します。
『削除ルール』というのが自分が削除されたときの関連先(逆)との関係をどうするかと言う事になります。

そして『削除ルール』の<無効にする>は、
相手側から自分への関連を無効にする。という事です。
 
 
今回の例で説明すると、
Tagが削除された時には、そのタグを設定していたTaskから削除されたTagとの関連をなくす
『さよならTaskさん…(; ^ ;)/~』
という処理をさせます。

これをしておかないと、データの無くなったTagにTaskがアクセスしようとするコトが出来てしまい、その結果エラーが発生します。
『別れるなんて聞いてないぞ!』
なので、削除された関連は無効にしておくワケですね。
 
 
では、<カスケード>はどういった処理か?
それは、自分自身が削除された時に関連している逆のデータも一緒に削除するって処理です。

まぁ、簡単に言えば
『別れるならあなたを殺して私も死ぬわっ!』『やっ…やめ……』(グサッ)
…ってな感じで^^;
 
 
つまり、今回は<Category>エンティティの関連<tasks>で、逆を<Task>エンティティの関連<category>に設定してあり、
削除ルールが<カスケード>なので、あるカテゴリが削除された場合には、そのカテゴリに属しているTodoはすべて一緒に消されるようになります。

削除ルールには、この2つの他に<アクションなし>(なにもしない)<拒否>(関係をnilにする)という選択肢がありますが、特に今回使った2つ以外の処理が必要になった時に使ってみるくらいの感じで良いと思います。

そしてこの『削除ルール』で注意しておきたいのが、
『自分が』削除されたときの処理というところです。
これを勘違いして『関連先が』削除されたときの処理として設定してしまうと思わぬバグになることがあるようなので、気を付けておきましょう。

(例えるのはやめときます…^^;)
 
 
今回の『Study CoreData』はこの辺りで終了としたいと思いますが、最後にいくつか補足をしておきます。

まず、下の画像を見てください。

エンティティビューに抽象というチェックボックスがありますね。
これは、つまりObjective-Cでいう抽象クラスのような<抽象エンティティ>をつくり、それを継承した<エンティティ>を作る事も出来るというものです。
ほとんど同じ属性を持っているけどちょっとだけ違う別々のエンティティを使う場合などには重宝しそうですね。

次に属性や関連を追加するポップアップの中に、今回使わなかった<取得済みプロパティを追加><取得要求を追加>というのがあります。
これは日本語訳されているので気付きづらいですが、

取得済みプロパティ = FetchedProperty (受信済みプロパティとも訳される)
取得要求 = FetchRequest (良いヤツ(しつこい!^^;))

となっています。

フェッチリクエストについては以前調べたので分かると思いますが、データを取得する上での条件をモデルエディタ上でもできるようですね。
 
フェッチドプロパティは述語(NSPredicate)という条件指定を使ってデータを取得するためのプロパティらしいです。
(実は以前に例に挙げたMacのスポットライトは述語な感じです)
この述語というのはフェッチリクエストにも入れられるのですが、以前にも触れたようにフェッチリクエストには述語の他に<エンティティの指定><並べ替えの指定>が含まれています。

まぁ、述語については今回はコード上で行う事にしようと思いますので、興味のある方は調べてみてくださいね。
 
 
そしてエンティティビューにある<クラス>の項目ですが、ここも変更が出来るようになっています。
たとえばNSManagetObjectを拡張したサブクラスなどもエンティティのクラスとして、ここで指定できるというコトのようです。

それと最後にデータの型についてですが、既に用意されている型以外にも<変換可能>(id型)を使って、独自の型(例えばUIImageやUIColorなど)を設定する事も出来ます。
(その際にはNSManagetObjectを継承したサブクラスを用意してデータの変換を実装する必要があります。)
そこら辺はAppleのサンプルコード『iPhoneCoreDataRecipes』などが参考になると思います。
 
 
それでは、今回はこの辺で。

でわ、また明日。
恋人は大切にしましょ〜ね〜(笑)

Study CoreData 3 ~デルモに手を出すなら慎重に…~」への5件のフィードバック

  1. ピンバック: [夕刊] iPhone 4の広島弁動画がやってきたぞ!

  2. ピンバック: データモデルの属性追加 « minihouse55

  3. 何度も読み直しながら1からじっくりと今更ながらCoreDataを勉強させていただいてます。
    XCode4等になって多少違いはありますが、今の所難なく進めれていましたが一つ理解出来ない部分があります。

    一つ質問したいのですが、
    >このように新しいファイルが作られ、古いモデルバージョンには2という数字が付けられます。
    >ここで注意しておく事は数字が付いているファイルは、数が少ないもの順に古いファイルになる事です。

    と言う部分ですが、数が少ないものが古いのであれば2と言う数字が付けられたモデルバージョンは新しいと言う事ですよね?
    番号が付いていないモデルバージョンが一番新しいと言う事でしょうか?
    古いモデルバージョンには2と言う数字が付くと言うのが良く解らないのでアドバイス頂けないでしょうか?

    • TirolCatさん。
      コメント&ご覧頂いてありがとうございます!

      >と言う部分ですが、数が少ないものが古いのであれば2と言う数字が付けられたモデルバージョンは新しいと言う事ですよね?
      >番号が付いていないモデルバージョンが一番新しいと言う事でしょうか?
      >古いモデルバージョンには2と言う数字が付くと言うのが良く解らないのでアドバイス頂けないでしょうか?

      これについてですが現行のXocde(4.5 GM)で確認してみたのですが
      今のものだとモデルバージョンを追加する際に独自でバージョン名を付けられるようになっているようですね。

      これを書いた当時は、確かバージョンを追加すると勝手に通し番号が付けられるような仕様だったかと思います。
      なので、この部分は現在のXcodeでは気にしないでください(^〜^;)

      『新たに追加したバージョンが最新』というとても当たり前な感じですので。

      • 「新しく追加したバージョンが最新」と言う形で少し理解出来た感じがします。
        まだまだ関連と言った部分等完全に理解していない難しい部分も多いですが
        頑張って覚えていこうと思います。

        丁寧なご返信有り難うございます。

コメントを残す