Copyright (C) iPhoneアプリ開発備忘録 All rights reserved.
ブログ内で記したコード、内容の正確性は保証いたしません。
記載内容を実装したことにより発生した不具合・損害等の責任は一切負いません。



2010年01月05日

Core Dataを使ったデータの取り扱い



iPhone OS3.0より、データベースの取り扱いをCore Dataで実装することにより簡素にコーディングできるようになった。

Core Dataによるデータの取り扱いTips。

■Appleのサンプルソース「CoreDataBooks」
登録、削除、変更を実装しているプロジェクト。


■Xcodeの新規プロジェクト作成において、Navigation-based Applicationを選択し、オプション「Use Core Data for storege」にチェックして、テンプレートから生成すると、Core Dataを使用して登録、削除ができるコードがあらかじめ組み込まれているソースが自動生成される。


■既存プロジェクトに、Core Dataを組み込む場合は、上記で示すサンプルソースか新規プロジェクトで生成したソースを組み込んで実装していけばよい。
この場合、データモデルや管理オブジェクトクラスは自分で作る必要がある。
作り方は下記URLを参照。
http://appteam.blog114.fc2.com/blog-entry-53.html
 

■Core Dataでアプリケーションを開発するに当たり、AppleのCore Dataプログラミングガイドの理解は必須。
http://developer.apple.com/jp/Documentation/Cocoa/Conceptual/CoreData/


■手順

1. CoreData.framewoikをプロジェクトに追加する。
2. hoge_Prefix.pch CoreDataフレームワークのヘッダファイルをインポートする。
#import <CoreData/CoreData.h>


3. グローバル変数の定義
特定のメソッドのみCoreDataを使うのであれば、サンプルソースのように特定メソッドにNSManagedObjectContextを定義して値をAppDelegateで設定すればよいが、いろいろなところでCoreDataを使う場合や今後の拡張性を考慮するのであれば、グローバル変数として定義するのがよい。
NSManagedObjectContext *managedObjectContextGlobal; // CoreData

このグローバル変数は、例えばGlobal.hという名のヘッダファイルの中に定義し、プロジェクト内の全てのメソッドのなかでこのヘッダファイルをimportしておく。

4. AppDelegate.h
@interface tsuhanManiaAppDelegate : NSObject <UIApplicationDelegate> {
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;

}

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSString *)applicationDocumentsDirectory;


5. AppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {

managedObjectContextGlobal = self.managedObjectContext; // 追加する

}

// メソッド全部を追加する
- (void)applicationWillTerminate:(UIApplication *)application {

NSError *error;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Update to handle the error appropriately.
LOG(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}

- (void)dealloc {

[managedObjectContext release]; // 追加する
[managedObjectModel release]; // 追加する
[persistentStoreCoordinator release]; // 追加する

}

// ここから下は全部追加する
#pragma mark -
#pragma mark Core Data stack

/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *) managedObjectContext {

if (managedObjectContext != nil) {
return managedObjectContext;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}


/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
*/
- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return managedObjectModel;
}


/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
/*
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}

NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"Bookmark.sqlite"]];

NSLog(@"tsuhanManiaAppDelegate persistentStoreCoordinator %@", [self applicationDocumentsDirectory]);

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.

abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.

Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
// NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
// abort();
// }


if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}


NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"hoge.sqlite"]; // sqliteファイルの名前は任意
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/

NSLog(@"persistentStoreCoordinator applicationDocumentsDirectory %@", [self applicationDocumentsDirectory]);
NSLog(@"persistentStoreCoordinator storePath %@", storePath);

NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSLog(@"persistentStoreCoordinator storePath-!storePath");
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"hoge" ofType:@"sqlite"]; // sqliteファイルの名前は任意
if (defaultStorePath) {
NSLog(@"persistentStoreCoordinator defaultStorePath");
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}

NSURL *storeUrl = [NSURL fileURLWithPath:storePath];

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.

abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.

Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}


return persistentStoreCoordinator;
}


#pragma mark -
#pragma mark Application's Documents directory

/**
Returns the path to the application's Documents directory.
*/
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}





6. xdatamodelの作成
(1) Xcodeにて、アクション→追加→新規ファイル→iPhone OS→リソース→データモデル
(2) 拡張子xdatamodel以前のファイルの名前を入れて「次へ」をクリック。
(3) 次に表示される画面では何も入れずに保存。
(4) プロジェクト管理画面に表示された、xdatamodelファイルをクリックして開く。
(5) xdatamodelのエディタが開くので、エンテティ、プロパティを入力する。
エンテティはデータベースでいうところのテーブル、プロパティはレコードのこと。
"+"ボタンで項目が追加できる。プロパティは属性は必ず設定しておくこと。

■参考記事
http://appteam.blog114.fc2.com/blog-entry-53.html

7. データ登録、表示、削除
上記1〜6はCoreDataを扱う場合の前準備の部分。
CoreDataを使ってデータの登録、表示、削除をする場合は、Appleのサンプルソース「CoreDataBooks」または、Xcodeの新規プロジェクト作成において、Navigation-based Applicationを選択し、オプション「Use Core Data for storege」にチェックして、テンプレートから生成したソースを参照。

最低限必要なコードの例
・hogeViewController.h
@interface hogeViewController.h : UIViewController <NSFetchedResultsControllerDelegate>
{
NSFetchedResultsController *fetchedResultsController; // CoreData
NSManagedObjectContext *managedObjectContext; // CoreData

}

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@end



・hogeViewController.m
@synthesize fetchedResultsController;
@synthesize managedObjectContext;

- (void)viewDidLoad {

// CoreData managedObjectContextをグローバル変数からメソッド変数にコピー
self.managedObjectContext = managedObjectContextGlobal;


// Set up the edit and add buttons. ナビゲーションバーに編集ボタンを追加
self.navigationItem.rightBarButtonItem = self.editButtonItem;


// CoreData Access
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
/*
Replace this implementation with code to handle the error appropriately.

abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

}

#pragma mark -
#pragma mark Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {

if (fetchedResultsController != nil) {
return fetchedResultsController;
}

/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"hoge" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];

// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];

// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;

[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];

return fetchedResultsController;
}


■データの追加
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

// If appropriate, configure the new managed object.
[newManagedObject setValue:self.id forKey:@"id"];

// Save the context.
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.

abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}



■データの参照
// Configure the cell.
NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];

NSLog( "id %@", [[managedObject valueForKey:@"id"] description]);



■データの削除
// Delete the managed object for the given index path
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];

// Save the context.
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.

abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}








※ 開発途中にxdatamodelの構造(プロパティの追加など)を変えると、アプリ起動時にエラーとなりアプリが落ちる。この場合は、アプリをシミュレータ、iPhone実機上から削除し、Xcodeのターゲットのクリーニング、キャッシュを空にしてから再ビルドする。このことは、AppStoreにリリース済のアプリをxdatamodelの構造変更を伴うアップデートしたときに、アプリが落ちることを意味する。
従って、xdatamodelの構造は十分検討し、後で変更の必要がないようにしておくこと。やむを得ずxdatamodelの構造変更をする場合は、AppStore上の説明において、アップデート前のアプリをアンインストールした後にインストールする旨の注意書きをしておくことが必要。
 

タグ:iPhone
posted by mobileDeveloper at 17:32 | Comment(7) | TrackBack(0) | Core Data はてなブックマーク - Core Dataを使ったデータの取り扱い | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
はじめまして。

「アプリをシミュレータ、iPhone実機上から削除し、Xcodeのターゲットのクリーニング、キャッシュを空にしてから再ビルドする。」
ここで悩んでました。
ありがとうございました。

今は
- (NSFetchedResultsController *)fetchedResultsController {

aFetchedResultsController.delegate = self;
がなにやってるのか調べてるところです。
自分でなんとかしたいものです。

更新楽しみにしています。
Posted by cuberock at 2010年01月27日 22:30
お役に立てて何よりです。
Posted by mobileDeveloper at 2010年02月20日 12:22
はじめまして。
coredataのサンプルソースを探していましたら、こちらのエントリへたどり着きました。


ほぼ丸々参考にさせていただいているのですが、(汗


> managedObjectContextGlobal = self.managedObjectContext; // 追加する

> self.managedObjectContext = managedObjectContextGlobal;


がundeclaredでエラーとなってしまいました。

「managedObjectContextGlobal」の宣言が上手くできていないようで、Global.hに宣言し、各.mにてimportしているのですが、どうも定義ができていないようです。

具体的に、グローバル変数「managedObjectContextGlobal」の宣言方法をおしえていただけませんでしょうか?

#すみませんが、私objective-cが初心者でして、ご教授いただけると助かります。
Posted by crool at 2010年04月09日 22:25
managedObjectContextGlobalの宣言方法は、「3. グローバル変数の定義」で書いている通りです。

"AppDelegate.m"で、"Global.h"をimportしているかご確認ください。
それでダメならビルド環境を見直してみることをおすすめします。
Posted by mobileDeveloper at 2010年04月10日 20:49
ありがとうございます!!
無事読み込めましたー。

@interfaceの中に入れてしまっていました。。。これじゃグローバル宣言にはなりませんね。ありがとうございます。

Posted by crool at 2010年04月12日 15:49
お役にたててよかったです。
Posted by mobileDeveloper at 2010年04月13日 01:39
はじめまして、CoreDataの記事とても参考になります。

一つ質問なのですが、CoreDataを実装して見慣れないエラーに悩まされております。
Apple Mach-O Linker (Id) Error
というエラーは今回が初めてで、探しても見つからないのですが、もし何かわかるようでしたら教えて下さい。。

よろしくお願い致します。
Posted by yuiy at 2011年03月16日 18:52
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
※ブログオーナーが承認したコメントのみ表示されます。

この記事へのトラックバック
Apple、Appleのロゴ、App Store、iPodのロゴ、iTunesは、米国および他国のApple Inc.の登録商標です。
iPhone、iPod touch、iPadはApple Inc.の商標です。
iPhone商標は、アイホン株式会社のライセンスに基づき使用されています。
その他、本ブログに記載されている製品名、会社名は、それぞれ各社の商標または登録商標です。