ページ

2010年6月13日日曜日

サイトをリニューアルしました

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

新しいサイトはこちらです↓

Cocoaの日々

※このサイトは閉鎖せずに今後も公開しておきます。

長い間ありがとうございました。
よければ新しいサイトの方も訪問してみて下さい。

橋口

2010年6月12日土曜日

CoreData - トランザクション(5) まとめ

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回までの検証結果まとめ。

NSManagedObjectContext操作まとめ

操作説明ディスク操作Undo/Redoの履歴
save現時点でのメモリの内容を保存する書き出しなし
rollbackディスク上の内容に戻す(必要なら)読み込みクリア
undo1つ前の操作を取り消す(操作直前の内容に戻す)なしなし
redo次の操作をもう一回行う(取り消した操作を復活)なしなし
resetメモリの初期化(変更の破棄、読み込まれたオブジェクトの破棄)なしクリア

(参考)NSManagedObjectContext Class Reference - Undo Management

今まで検証に使ったソースは GitHub からダウンロードできる。
CoreDataTransaction at 2010-06-12 from xcatsan's SampleCode - GitHub

2010年6月11日金曜日

CoreData - トランザクション(4) reset

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

-[NSManagedObjectContext reset] を試す。

コードを追加し、

- (IBAction)resetAction:sender
{
[self.managedObjectContext reset];
}

ボタンに紐づける。
実行。resetボタンを押すと、
データが消える。
これは NSManagedObjectContext(メモリ)上のデータが消えただけで、ディスク上のデータは残っている。

試しに reload ボタンを追加する。
ボタンが押されたら、NSArrayController を使っているので prepareContent を投げてみる。

- (IBAction)reloadAction:sender
{
[arrayController prepareContent];
}


すると reset で一旦消えた後、
reloadボタンで復活。

reset するとそれまでに読み込まれた NSManagedObject がすべて開放される(ドキュメントでは "forgotton" という表記)。当然ながら直前の変更状態はディスクには残らず破棄されてしまう。Undo/Redo履歴もクリア。用途としては NSMangedObjectContext のインスタンスを別の用途で使いまわす場合の初期化に使うことが考えられる。

2010年6月10日木曜日

CoreData - トランザクション(3) undo と redo

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

undo と redo をやってみる。

まずアクションメソッドを用意する。

- (IBAction)undoAction:sender
{
[self.managedObjectContext undo];
}
- (IBAction)redoAction:sender
{
[self.managedObjectContext redo];
}


Interface Builder を開き、Undo/Redoのボタンを追加する。これを先程のメソッドに紐づける。
実行する。確かに Undo/Redo が効いている。なお標準でメニューのUndo/Redoが FirstResponderにひもづけられており、実は今回のボタンを用意しなくてもメニューから Undo/Redoが行えた。

save と rollback の関係だが次のようになっていた。
(1) save後、Undo/Redo操作 は行える
(2) rollback後、Undo/Redo操作は行えない(操作履歴がクリアされる)

2010年6月9日水曜日

CoreData - トランザクション(2) rollback

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

rollback を試してみよう。前回のサンプルに Rollback ボタンを追加する。

rollback用のアクションコードを書き、そこへ紐づける。

- (IBAction)rollbackAction:(id)sender
{
[self.managedObjectContext rollback];
}


実行してみよう。
新規にレコードを追加し、入力したところで Rollback ボタンを押す。
すると新規レコードが取り消されるのがわかる。


複数の操作も試してみよう。
まず新規レコードを追加し
元からあったレコードを修正し
1レコード削除する
そして Rollback
元に戻った。

2010年6月8日火曜日

CoreData - トランザクション(1) 準備

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

CoreData のトランザクションについて調べてみる。

NSManagedObjectContext では Undo Management としていくつかメソッドが用意されている。
NSManagedObjectContext Class Reference - Undo Management

これらの挙動を試す簡単なサンプルプログラムを組んでみよう。
今回はまずレコードの表示、追加、削除、変更ができるプログラムを組む。


まず Xcodeで新規プロジェクトを作る。このとき "Use Core Data for storage" にチェックを入れておく。

続いてモデルを定義する。今回は Book というシンプルなエンティティを一つ用意した。

ビューを用意する。NSTableView を作り author と titile 列を作る。
モデルとビューの紐付けに NSArrayController を使う。

Modeを Entity とし、Entity Name に Book を指定する。これでモデルとつながった。
NSTableView内の NSTableColumnのバインディング設定を開、Array Controller を指定する。
これで MVCがつながった。
モデル(Book) ← コントローラ(ArrayController)←ビュー(NSTableView/NSTableColumn)

ただし CoreDataを扱うにはもう一手間必要で ArrayController に NSManagedObjectContext を渡す必要がある。今回 NSManagedObjectContextはコード内で生成している為、Interface Builderでは接続できない。この為、コード上で紐付けておく。
CoreDataTransaction_AppDelegate.m
- (void)awakeFromNib
{
[arrayController setManagedObjectContext:self.managedObjectContext];
}

arrayController はあらかじめ IBOutlet で定義し、InterfaceBuidlerで紐付けておく。
CoreDataTransaction_AppDelegate.h

@interface CoreDataTransaction_AppDelegate : NSObject 
{
       :
    IBOutlet NSArrayController* arrayController;
}


モデル(Book) ← コントローラ(ArrayController)←ビュー(NSTableView/NSTableColumn)
               ↓
                     CoreData(NSManagedObjectContext)


これでベースができた。後はデータ追加と削除のコードを付けておこう。
[+][ー]ボタンを作る。


NSArrayController には add: と remove: が用意されているので、それぞれ接続する。
これだけで新規追加と削除が行えるようになる。

さて動かしてみよう。追加、変更、削除が行える。



File メニューから Save を選択するとディスク(XML)に保存ができる。これは新規作成時のテンプレートが saveAction: を実装していて、メニューのSaveと紐づいている為。



ソース:
CoreDataTransaction at 2010-06-08 from xcatsan's SampleCode - GitHub


今日はここまで。

2010年6月7日月曜日

カテゴリでクラスメソッド定義

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

カテゴリはクラスメソッドもいける。

こうなら、

@interface Customer (Extension)
+(void)hello;
@end



+(void)hello
{
NSLog(@"hello");
}


普通に呼び出せる。


[Customer hello];



ファクトリメソッドなどの追加に使えそうだ。

- - -
近日ブログのリニューアル予定(URLが変わります)。

2010年6月6日日曜日

今週のCocoa情報(6/6) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

what are alternatives to throwing exceptions in objective c - Stack Overflow
@throw よりも NSError を使う方が "the Apple recommended pattern" とのこと。確かに Cocoa Framework では例外より NSErrorを使う方が多い。


iPhone開発で便利なcocoa.vim - hellkite 日記と雑記とメモ。
MacVimとcocoa.vimで世界が変わった - Meltdown Countdown
VimでCocoa開発する話題。普段 PHPアプリなどは vimを使っているので気になる。


Cocoa nonatomic properties - Stack Overflow
プロパティ設定で "nonatomic"をつけない場合、ロック/アンロックのオーバーヘッドがかかる。

- - - -
今回も少なかった。

2010年6月5日土曜日

CoreData - Object ID(その5)情報

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: CoreData - Object ID(その4)Object ID から NSManagedObject を取得する #2

NSManagedObjectID についての情報など。


NSManagedObjectID into NSData - Stack Overflow

NSURL へ変換後、NSKeyedArchiver を使い NSData へ格納。



Cocoa with Love: Safely fetching an NSManagedObject by URI

NSManagedObjectIDに関して簡潔だがわかりやすい説明。URIから NSManagedObject を取得するカテゴリを紹介している。

@implementation NSManagedObjectContext (FetchedObjectFromURI)
- (NSManagedObject *)objectWithURI:(NSURL *)uri
{
 :
}

例外を出さず、確実に非faultのオブジェクトを取得する作りになっている。


Core Data Programming Guide: Using Managed Objects
Managed Object IDs and URIs

Mac Dev Center の解説。

2010年6月4日金曜日

CoreData - Object ID(その4)Object ID から NSManagedObject を取得する #2

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: CoreData - Object ID(その3)Object ID から NSManagedObject を取得する


Object ID から NSManagedObject を取得するメソッドは、前回の -[objectWithID:] を含め3つある。

クラス:NSManagedObjectContext

- (NSManagedObject *)objectWithID:(NSManagedObjectID *
- (NSManagedObject *)objectRegisteredForID:(NSManagedObjectID *)objectID
- (NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID error:(NSError **)error

今回はこれらの動作を比較してみる。

1. objectWithID: の動作
(1) まず Object ID に該当するオブジェクトが、NSManagedObjectContext内に登録されているかチェックする。登録されている場合は、そのオブジェクトを返す(非Fault)

(2) 登録されていない場合はPersistent Storeからフェッチする。
a) 該当レコードが存在する場合
フェッチしたオブジェクトを返す(Fault)

b) 該当レコードが存在しない場合
例外がスローされる。
(例)
CoreDataObjectID[12483:80f] An uncaught exception was raised
CoreDataObjectID[12483:80f] CoreData could not fulfill a fault for '0x226340 <x-coredata://677CA547-4D80-417A-8810-70847FB0375D/Book/p20>'
CoreDataObjectID[12483:80f] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x226340 <x-coredata://677CA547-4D80-417A-8810-70847FB0375D/Book/p20>''
*** Call stack at first throw:
(
 0   CoreFoundation                      0x9253bbda __raiseError + 410
 1   libobjc.A.dylib                     0x97efc509 objc_exception_throw + 56
 2   CoreData                            0x9469a00e _PFFaultHandlerLookupRow + 174
 3   CoreData                            0x94699f57 -[NSFaultHandler fulfillFault:withContext:] + 39
             :

2. objectRegisteredForID: の動作
NSManagedObjectContext内に登録されているかのみチェックする。

a) Object ID に該当するオブジェクトがNSManagedObjectContext内に登録されている場合
該当するオブジェクト(NSManagedObject)を返す(非Fault)

b) 該当レコードがNSManagedObjectContext内に登録されていない場合
nil を返す。例外はスローされない。


3. existingObjectWithID:error: の動作
このメソッドは Mac OS X v10.6 から追加された(iPhone OS では 3.0以降)。基本動作は objectWithID: と同じだがヒットしなかった時は例外はスローせず、nilを返す。また errorから原因を知ることができる。

(1) まず Object ID に該当するオブジェクトが、NSManagedObjectContext内に登録されているかチェックする。登録されている場合は、そのオブジェクトを返す(非Fault)

(2) 登録されていない場合はPersistent Storeからフェッチする。
a) 該当レコードが存在する場合
フェッチしたオブジェクトを返す(非Fault)※ここは試した時には何故か Faultにならなかった(ので、非Faultにしてある)。

b) 該当レコードが存在しない場合
nilを返す。この時、errorに原因が格納される。
(例)Error Domain=NSCocoaErrorDomain Code=133000 UserInfo=0x21e6f0 "Attempt to access an object not found in store."


サンプル:CoreDataObjectID at 2010-06-04 from xcatsan's SampleCode - GitHub
※コメントを修正することで上記3種類が試せるようになっている(かなりわかりづらいが)。

- - - -
Mac OS X v10.6 / iPhone OS 3.0 では existingObjectWithID:error: が一番使い勝手がいい。

2010年6月3日木曜日

CoreData - Object ID(その3)Object ID から NSManagedObject を取得する

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Object ID の URI から NSMangedObject を取得することができる。ステップは2つ。

(1) -[NSPersistentStoreCoordinator managedObjectIDForURIRepresentation:] を使い、NSManagedObjectID を取得する
(2) -[NSManagedObjectContext objectWithID:] に(1)の結果を渡し、NSManagedObject を取得する。

NSPersistentStoreCoordinator Class Reference - managedObjectIDForURIRepresentation:

NSManagedObjectContext Class Reference - objectWithID:

やてみよう。

サンプル:CoreDataObjectID at 2010-06-03 from xcatsan's SampleCode - GitHub


前回までのサンプルに次の2つの修正を加える。
1. アプリ終了時に選択していたレコードの Object ID (URI) を User Defaults へ保存する

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {

NSArray* selectedObjects = [arrayController selectedObjects];
if ([selectedObjects count] > 0) {
Book* book = [selectedObjects objectAtIndex:0];
NSManagedObjectID* moid = [book objectID];
NSString* uriString = [[moid URIRepresentation] description];
[[NSUserDefaults standardUserDefaults]
setValue:uriString forKey:@"PREVIOUS_URI"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
2. アプリ起動時に User Defaults に保存された Object ID (URI) から、NSManagedObject を取得し、ログへ書き出す。



- (void)awakeFromNib
{
[arrayController setManagedObjectContext:self.managedObjectContext];
NSString* uriString = [[NSUserDefaults standardUserDefaults] objectForKey:@"PREVIOUS_URI"];
if (uriString) {
NSURL* uri = [NSURL URLWithString:uriString];
NSManagedObjectID* objectID =
[self.persistentStoreCoordinator managedObjectIDForURIRepresentation:uri];
Book* book = (Book*)[self.managedObjectContext objectWithID:objectID];
NSLog(@"book: %@, %@ (%@)", book.Title, book.Author, uriString);
}
}



実行してみよう。まず行を選択し、アプリを終了する。
続いてアプリを起動し、ログを確認する。

CoreDataObjectID[12239:80f] book: Book2, Inu (x-coredata://677CA547-4D80-417A-8810-70847FB0375D/Book/p2)

出た。

- - - -
一連の検証で Object ID がアプリの状態保持と復帰(レジューム)に利用できそうなことがわかった。

2010年6月2日水曜日

CoreData - Object ID(その2)モデルのバージョンを変える

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

モデルのバージョンを変えると Object ID は変化するのか?試してみた。


バージョンを追加後(v2)、createdを追加した。



マッピングモデルを追加し、現在のバージョンを先程追加したもの(v2)に設定する。

(参考)Cocoaの日々: CoreData - マイグレーション

ビルド後に実行してみる。

(なお実行時にエラーが出る場合は、クリーニングしたのち再ビルドすると良い)。


結果は変わらず。これはまあ当然か。
1レコード追加してみる。
UUIDの部分(677CA547-...)は変わらないようだ。モデルのバージョンによって変化しないことがわかった。


SQLite DB の中身。
sqlite> select * from z_metadata;
1|677CA547-4D80-417A-8810-70847FB0375D|bplist00?
_NSStoreModelVersionIdentifiers_NSPersistenceFrameworkVersion_NSStoreModelVersionHashes[NSStoreType_ NSStoreModelVersionHashesVersion__NSAutoVacuumLevel???

メタ情報に変化はないようだ。最初のカラム名に Version とついていたので、レコードが追加されると思ったがそうはならないようだ。


サンプル:
CoreDataObjectID at 2010-06-02 from xcatsan's SampleCode - GitHub

2010年6月1日火曜日

CoreData - Object ID(その1)NSManagedObjectID

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

NSManagedObject の Object ID
NSManagedObjectには、オブジェクトを一意に識別する為にObject ID割り振られている。この ID は NSManagedObjectID型として定義されていて、-[NSManagedObject objectID] で取得できる。

NSManagedObject Class Reference - objectID


NSManagedObjectID
このNSManagedObjectIDは次のメソッドを持っている。

- (NSEntityDescription *)entity
- (BOOL)isTemporaryID
- (NSPersistentStore *)persistentStore
- (NSURL *)URIRepresentation

NSManagedObjectID Class Reference

saveするまでの間、Object ID は一時扱いとなる。その場合、-[isTemporaryID] が YES を返す。

-[URIRepresentation]は Object ID のURI表記を返す。

URIRepresentation の例
x-coredata://1C336A08-41AD-48E9-8A3F-8AFF17244055/Book/p1

(以下推測)
x-coredata | プロトコル
1C336A08-41.. | モデルの UUID(もしくは類似のID)
book | エンティティ名
p1 | 'p' + プライマリキー

なおsave前の(一時的な)Object ID の URIRepresentation は次のようになる。
x-coredata:///Book/t7839D4D0-A144-4C95-B14C-33AEFAF337632

リファレンスによれば、Object ID は他のアプリケーションも含めユニークなものになるとのこと。終了時に plistへ保存しておいて、次回起動時にこのIDから前回使っていた情報を取得するなんて用途に使える。


サンプル
Object ID を見るための簡単なサンプルを用意した。

ソース:CoreDataObjectID at 2010-06-01 from xcatsan's SampleCode - GitHub

モデルの定義はこんな感じ。

実行するとテーブルが現れる。Addで追加ができる。この時点ではまだテンポラリなIDが割り振られている。

saveすると正式なものが割り振られる。
この時の SQLiteの中身。
$ sqlite3 storedata 
SQLite version 3.6.12
Enter ".help" for instructions
sqlite> .schema zbook
CREATE TABLE ZBOOK ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZTITLE VARCHAR, ZAUTHOR VARCHAR );
sqlite> select * from zbook;
1|1|1|Book1|Neko
sqlite>
sqlite> .schema z_metadata
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB);
sqlite> select * from z_metadata;
1|677CA547-4D80-417A-8810-70847FB0375D|bplist00?
_NSStoreModelVersionIdentifiers_NSPersistenceFrameworkVersion_NSStoreModelVersionHashes[NSStoreType_ NSStoreModelVersionHashesVersion__NSAutoVacuumLevel???

ObjectID に含まれるUUIDが Z_METADATAに格納されているのがわかる。