A Day In The Life

とあるプログラマの備忘録

サルでもわかる Core Data 入門【概念編】

iOS でデータを永続化する方法の続きです。今回は Core Data を使ったデータの永続化方法について説明します。
本記事では複雑で習得が難しいとされている Core Data について概念編と実装編の2回に分けて説明していきます。記事の全体的な流れとしては Core Data がどういったフレームワークかというところから O/R マッピングの説明、Core Data を使うメリット、Core Data フレームワークの概要、Core Data を使ったプログラムの例まで説明します。次の記事で実際に動くサンプルを作成します。

Core Data って何?

Core Data はモデルオブジェクトを永続化するためのフレームワークです。もとは Mac OS X のために開発されたフレームワークで、iOS 3.0 から使用できるようになりました。
Core Data はデータの永続化に SQLite というリレーショナルデータベース(以降 RDB と略します)を使用します。Core Data はメモリ上にあるオブジェクトを RDB のレコードに変換して保存、また逆に RDB のレコードをオブジェクトに変換してメモリに展開してくれます。
Core Data
Core Data のようにオブジェクトとレコードの変換を行うフレームワークのことを O/R マッピングフレームワークと呼びます(O/R は Object / Relational の略)。
Core Data は、O/R マッピングフレームワークの中でもプログラマRDB のテーブルやレコードを意識することなく使えるのが特徴です。

O/R マッピングフレームワークはなぜ必要か?

アプリで使用するデータの構造が単純でデータ量もわずかであればオブジェクトアーカイブを使ってデータの保存をすれば問題ありません。 しかしデータの構造が複雑でデータ量が多くなるとオブジェクトアーカイブでデータ管理するには限界が出てきます。そこで複雑なデータや大量のデータを管理することができるデータベースを使います。
データベースと言ってもいくつかの種類があります。代表的なものを挙げるとリレーショナルデータベース、オブジェクトデータベース、キーバリューストアなどがあります。一番良いのは iOS で使用することができるオブジェクトデータベースがあれば O/R マッピングフレームワークは不要です。しかし残念ながらオブジェクトデータベースはその技術自体がまだまだ進化の過程にあり実用で使えるような製品は今のところありません。このような事情から技術的に成熟し、かつ安定した製品が多数存在するリレーショナルデータベースをデータの永続化に使うのが一番実用的になります。
RDB をデータの永続化に使う場合、RDB のレコードとオブジェクト指向プログラミングであつかうオブジェクトの間には様々な違いがあるためそれを吸収する O/R マッピングフレームワークが必要になります。

オブジェクト技術とリレーショナル技術の違い

O/R マッピングフレームワークはオブジェクトとレコードの変換を行う、と説明しましたがオブジェクトとレコードにはどのような違いがあるのか、なぜオブジェクトとレコードを変換する必要があるのかをオブジェクト技術とリレーショナル技術を比較しながら説明していきます。
本記事では便宜上、オブジェクト指向をベースにした技術をオブジェクト技術、リレーショナル理論を元にした技術をリレーショナル技術と呼ぶことにします。

技術的背景の違い

オブジェクト技術はプログラム言語から派生した技術であるのに対しリレーショナル技術はデータの永続化の技術から派生しています。オブジェクト技術はオブジェクト指向、リレーショナル技術はリレーショナル理論が技術のベースになっています。
両者ともデータをあつかうことに変わりはないのですが、一口にデータといってもオブジェクト技術がメモリ(主記憶)上のデータのことを指すのに対し、リレーショナル技術がディスク(補助記憶)上のデータのことを指すという違いがあります。

用語の違い

上記で説明したように両技術はそもそもの技術背景がことなるため使われる用語に違いがあります。オブジェクト技術とリレーショナル技術で使われている用語の違いを図にすると以下のようになります*1
オブジェクト技術とリレーショナル技術用語の違い
データの実体のことをオブジェクト技術ではオブジェクト、リレーショナル技術ではレコードと呼びます。同じ概念のものを違う用語で呼んでいます。

データ構造の違い

オブジェクト技術とリレーショナル技術は技術的背景が異なるためデータ構造にも違いがあります。データ構造の違いは主に以下の4つが挙げられます。

  • オブジェクト技術は独自データ型(クラス)を属性の型に指定することができる。リレーショナル技術は使用できる型が制限されている。
  • オブジェクト技術はクラスを継承して別のクラスを作成することができる。リレーショナル技術はテーブルの定義を継承することが出来ない。
  • オブジェクト技術には主キーが存在しない。オブジェクトの同一性をプログラマの責任で決めなければいけない。
  • オブジェクト技術は関連をクラス属性であつかうがリレーショナル技術は外部キーで関連をあつかう。

このようにデータ構造が違うとデータを変換するときに様々な問題がおこります。以下はデータ構造の違いによって起こる問題です(【参考】HIBERNATE イン アクション1.2 パラダイムミスマッチ)。

  • 粒度に関する問題
    RDB 製品は全般的にユーザ定義型に関するサポートがいまいちでオブジェクトよりもレコードの方がデータの粒度が大きくなる傾向にある
  • サブタイプに関する問題
    テーブルはクラスのように継承することができない
  • 同一性の問題
    オブジェクト技術には主キーという概念がない。オブジェクト同士の同一性を保証するキーのようなものがない
  • 関連の問題
    テーブルは外部キーという形でしか関係を持つことができない

以下はデータ構造の違いによって起こる問題の例です。クラスとテーブルの定義という観点から見てみましょう。
クラスとテーブルのミスマッチの図

データ検索に関する違い

リレーショナル技術を使用した RDB では SQL を使って効率よく大量データの中から特定のデータを検索することができますが、オブジェクト技術にはデータの検索を行う SQL 相当の手段が存在しません。プログラマ自身が効率の良い検索方法を考えて実装しなくてはいけません。
データ検索の効率が悪いと検索スピードが遅くなりアプリの速度に影響をあたえるのに加え、使用するメモリ量にも影響が出てきます。この違いはデータ量が少ないときはあまり問題になりませんがデータ量が多くなればなるほど問題が発生しやすくなります。

両者の特徴

以上の違いをまとめると、オブジェクト技術はデータ構造を柔軟に定義することができるけどデータの検索が不得意、リレーショナル技術はデータ構造の柔軟性にかけるけどデータの検索が得意ということになります。
Core Data は、オブジェクト技術とリレーショナル技術の違いを吸収した上で両者の良いところを取り入れデータの管理を行います。

Core Data を使うメリット

モデルオブジェクトを永続化する方法には Core Data 以外にもiOS でオブジェクトをシリアライズしてファイルに保存する方法で説明したオブジェクトアーカイブを使うことができます。ここではオブジェクトアーカイブと比べて Core Data にどのようなメリットがあるのか説明していきます。
はじめにオブジェクトアーカイブと比べて Core Data の有利な点を挙げてみます。

  • データ読み込み時にすべてのデータをメモリに展開する必要がない
  • データ検索時に RDB の機能を使って効率よく検索できる
  • データのトランザクション管理が出来る

オブジェクトアーカイブはオブジェクトの集まりを常に一つのバイナリとしてあつかう必要があるので、データの量が100件、200件と増えれば増えるほどメモリ使用量やデータ検索の効率が悪くなる傾向があります。
それではメモリ使用量に関して考えてみます。データが1件(オブジェクトが一つ)の時は Core Data はデータの変更履歴などを保存するため、オブジェクトアーカイブを使うのに比べてオブジェクトのサイズが大きくなります。
複数件あるデータ(複数のオブジェクト)から1件のデータを取り出す場合、必要なデータ1件だけを取得できる Core Data はメモリ使用量が一定ですが、オブジェクトアーカイブはすべてのデータをメモリにロードして検索する必要があるためデータ量が増えるとメモリ使用量が増えていきます。
以下は Core Data とオブジェクトアーカイブのメモリ使用量を比較した図です。
メモリ使用量の比較
同じようにデータの検索に関して考えてみます。データ量が少ないとき Core Data はレコードとオブジェクトの変換コストが存在するためオブジェクトアーカイブに比べて時間がかかります。
しかしデータ量が増えてある一定を超えると、すべてのデータをロードして線形検索をするオブジェクトアーカイブに比べて、SQL を使ってデータを効率よく検索できる Core Data の方が検索時間が短くなります(Core Data が SQL を使ってさくっと欲しいデータが取れるのは検索のためのインデックスや検索アルゴリズムが発達している RDB を使っているからです)。
以下は Core Data とオブジェクトアーカイブのデータ検索時間を比較した図です。
検索時間の比較
上の2つの図でわかる通り、データの量が増えるに従って Core Data の方がオブジェクトアーカイブよりも有利になります。どのぐらいの量かはっきりと数値で示すことは出来ませんが、データの量を一つの目安にして Core Data の導入を決めれば良いと思います。
初めのうちはオブジェクトアーカイブを使ってデータを管理してメモリワーニングが多発したりアプリの動きがもっさりした時に Core Data の導入を考えるという戦略もありだと思います。

Core Data フレームワークの概要

ここまで O/R マッピングの役割や Core Data を使うメリットについて説明をしていきました。ここからは Core Data の構造が実際どのようになっているか見ていきましょう。
Core Data で使う主なクラスを以下にあげてみます。

  • NSManagedObject
    モデルクラス。Core Data で永続化するオブジェクトは必ずこのクラスのオブジェクトかこのクラスのサブクラスのオブジェクトでなければいけない
  • NSManagedObjectContext
    データの検索、挿入更新削除や Undo Redo を行うクラス
  • NSFetchRequest
    データの検索条件を管理するクラス。ここで指定した条件が SQL に変換されてデータの検索に使われる
  • NSFetchedResultsController
    NSManagedObject オブジェクトを監視するコントローラクラス。NSManagedObject オブジェクトが挿入、変更、削除された時に NSFetchedResultsControllerDelegate オブジェクトに通知する
  • NSEntityDescription
    エンティティの定義を管理するクラス。エンティティ記述と呼ばれている(エンティティについては後述)。
  • NSManagedObjectModel
    エンティティ同士の関連を管理するクラス
  • NSPersistentStoreCoordinator
    NSPersistentStore を管理するクラス。データベースを複数管理することができる
  • NSPersistentStore
    データベースの情報を管理するクラス

各クラスの関係をクラス図にすると以下のようになります。
Core Dataクラス図
ここで重要なのは NSManagedObject クラス NSManagedObjectContext クラス NSFetchRequest クラス NSEntityDescription クラスの4つです。この4つのクラスを中心に使い方を覚えていくのが良いと思います。その他のクラスはコードの自動生成や Core Data の中で使用されているものなのであまり使うことありません。

モデルクラスとテーブルの橋渡し役エンティティ

Core Data のプログラムを説明する前に重要な概念であるエンティティ(Entity)について説明します。
Core Data では永続化対象のデータのことをエンティティと呼び、永続化対象のデータは全てエンティティを作成する必要があります。エンティティの作成は Xcode のモデルエディタで行います。
モデルエディタ
モデルエディタで作成されたエンティティの実体は XML のデータです(プロジェクト名.xcdatamodeld という名前でファイルが作成されます)。この XML を使ってモデルクラス(NSManagedObject)やSQLite のテーブルが生成されます(Core Data が自動で行ってくれます)。
プログラムからモデルエディタで定義されたエンティティの情報を取得するためには NSEntityDescription オブジェクトを使います。*2

Core Data の基本的な使い方

それでは Core Data のプログラムについて説明していきます。Core Data を使う場面ごとにそれぞれプログラムの例を見ていきましょう。
Core Data を使う場面としては以下のようなものが考えられます。

  • Core Data を使うための準備
    データベースをどこに保存するかやモデルエディタの設定を行います。
  • データの新規作成
  • データの検索と編集
  • データの削除
Core Data を使うための準備

Core Data を使うにあたり始めにモデルエディタの設定やデータベースの保存場所の設定が必要です。Core Data を使うための初期設定として以下の手順が必要になります。

  1. NSManagedObjectModel オブジェクトの生成
    モデルエディタの設定を行います。
  2. NSPersistentStoreCoordinator オブジェクトの生成
    SQLite データベースの保存場所を設定します。またモデルエディタとデータベースの関連を設定します。
  3. NSManagedObjectContext オブジェクトの生成
    データ管理を行うための設定をします。このオブジェクトがデータ管理(挿入、編集、削除等)の全ての操作を行います。

以下は Core Data を使うための初期設定プログラムの例です。

// NSManagedObjectModel
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Hoge" withExtension:@"momd"];
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// NSPersistentStoreCoordinator
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSURL *storeURL = [[paths objectAtIndex:0] URLByAppendingPathComponent:@"Hoge.sqlite"];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:nil];
// NSManagedObjectContext
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];

上記のプログラムはプロジェクト作成時に「Use Core Data」のチェックを入れると自動生成されます。

新規データの作成

NSEntityDescription の insertNewObjectForEntityForName を使って NSManagedObject のインスタンスを取得します。

// context は NSManagedObjectContext クラスのインスタンス
NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Event"
                                                     inManagedObjectContext:context];
NSError *error = nil;
if (![context save:&error]) {
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  abort();
}

NSManagedObject の alloc + init メソッドインスタンス生成するとセーブ時にエラーになるので注意してください。

データの検索と編集

データの検索には NSFetchRequest クラスのオブジェクトを使います。NSPredicate で検索するデータの指定を行い、NSSortDescriptor で検索するデータの並び順を指定します(NSPredicateクラス NSSortDescriptor クラスともに Foundation フレームワークのクラスです)。

NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" 
                                        inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// 検索条件の指定
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"検索条件" ];
[fetchRequest setPredicate:predicate];
// データの並び順の指定
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"time" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:descriptor];
[fetchRequest setSortDescriptors:descriptors];
// 検索実行
NSArray *results = [context executeFetchRequest:fetchRequest error:nil];
// データを編集
NSManagedObject *managedObject = [results objectAtIndex:0];
[managedObject setValue:@"Hoge" forKey:@"name"];
NSError *error = nil;
if (![context save:&error]) {
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  abort();
}
既存データの削除

データを削除するには NSManagedObjectContext の deleteObject: メソッドを使います。

// 3番目のデータを削除
[context deleteObject:[results objectAtIndex:2]];
NSError *error = nil;
if (![context save:&error]) {
  NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
  abort();
}
NSManagedObjectContext クラス save: メソッド

NSManagedObjectContext クラスの save: メソッドはメモリ上にあるデータをデータベースに書き込みにいくためのメソッドです。新規データ作成、データの削除、データの変更を確定する時に使います。

実装編に続きます。

それでは実際に Core Data を使ったサンプルアプリを作成して行きましょう。
続きはこちらの記事になります。

ブログの記事が本になりました!

A Day In The Life の iOS に関する記事が書籍になりました。この記事の内容は書籍で読むことができます(2015年1月17日にSwiftに対応した改訂版がでました)。

本の内容に関する詳しい記事はこちらです。

参考書籍

O/R マッピングについて詳しく知りたいという方には以下の本がおすすめです。Hibernate という Java で出来た O/R マッピングフレームワークを題材とした O/R マッピングの解説書です(残念ながら絶版で中古でしか手に入りません)。

RDB の元になったリレーショナル理論について詳しく知りたい方は下記の本が参考になります。
リレーショナル理論自体は用語こそちがいますがオブジェクト指向にかなり近い考え方です。下記の本ではリレーショナル理論に忠実にしたがって RDB を実装すればオブジェクトとレコードのミスマッチはほとんど起こらないと主張されています。

参考

下記ページは Core Data で使われる用語についてまとめられています。

Core Data の管理対象オブジェクトについては以下のページが参考になります。

SQLite について詳しく知りたい方は以下のサイトを参考にしてください。

*1:この図では一般的な RDB 製品で使われている用語を使っています。リレーショナル理論ではテーブルは関係、レコードはタプルといいます。

*2:Entity クラスというものは存在しません