ページ

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に格納されているのがわかる。

2010年5月31日月曜日

Objective-C カテゴリでプロパティ

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

例えばこんなクラスがあるとする。

Customer.h

@interface Customer :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString * address1;
@property (nonatomic, retain) NSString * postcode;
@property (nonatomic, retain) NSString * lastname;
@property (nonatomic, retain) NSString * address2;
@property (nonatomic, retain) NSString * firstname;
@property (nonatomic, retain) NSDate * birthdate;
@property (nonatomic, retain) NSString * firstnameKana;
@property (nonatomic, retain) NSString * lastnameKana;

@end



birthdate(誕生日)から現時点での年齢を計算したいのだが、これをプロパティとして取得できるようにしたい。これをカテゴリで実装する。

こんな感じ。

Customer+Extension.h

#import "Customer.h"

@interface Customer (Extension)

@property (assign, nonatomic, readonly) NSInteger age;

@end


追加メソッドをプロパティとして実装できる。

@implementation Customer (Extension)

- (NSInteger)ageForBirthday_:(NSDate *)dateOfBirth {
  :
}

- (NSInteger)age
{
return [self ageForBirthday_:self.birthdate];
}



これを使う場合はこんな感じ。

#import "Customer+Extension.h"



cell.detailTextLabel.text =
[NSString stringWithFormat:@"%d", customer.age];


参考)
Calculate age in objective-c | I ticked the wrong box

Cocoaの日々: CoreData - Xcodeでモデルクラスを自動生成する

2010年5月30日日曜日

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

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

How to get macos Log-in User Name in objective-c - Stack Overflow

Q)ログイン中のユーザ名を取得するには?
A)NSUserName() を使う

Foundation Functions Reference

他にも

NSFullUserName
NSHomeDirectory
NSHomeDirectoryForUser

なんてものがある。


Break on EXC_BAD_ACCESS in XCode? - Stack Overflow

EXC_BAD_ACCESS 解決に NSZombieEnabled を使う件。
CocoaDev: NSZombieEnabled



Cocoa Singleton conventions - Stack Overflow

Q)シングルトンパターンを使う場合のメソッド名は default〜 と shared〜 のどちらが良いか。

(例)
NSSingletonObject *so = [NSSingletonObject defaultSingleton];

  VS

NSSingletonObject *so = [NSSingletonObject sharedSingleton];

A)
shared〜 :このメソッド以外(お約束として)インスタンス生成ができない場合(真の?シングルトン)
default〜:自分でも作成可能だが、クラスがデフォルトインスタンスを提供する場合

なるほど。

参考)
Cocoa Fundamentals Guide: Cocoa Design Patterns - Singleton



how to rename or change file name case on the same location in objective-c - Stack Overflow

Q)ファイル名の大文字小文字を変換したい
 (例)abc.txt => (ABC.txt or Abc.txt or abc.TXT or abc.Txt)

- (BOOL)movePath:(NSString *)source toPath:(NSString *)destination handler:(id)handler
は、この用途では使えない。

A)ヒント↓
Possible Bug in NSFileManager -moveItemAtPath:toPath:error: | Cocoabuilder

systemコール "rename" を使ったらどうか、とのこと。
-fileSystemRepresentation で C文字列が取得できるのでそれを使う。



OCUnitの使い方(Cocoa Application 編) ([SM gakusyuu];)

環境の用意からテストケースの作成までの日本語解説。図入りで分かりやすい。






Cocoa Fundamentals Guide: Cocoa Design Patterns

一つ前の情報で紹介したものだが、こんなものがあったのか。

2010年5月29日土曜日

CoreData - テーブルの件数を取得する(SQL確認)

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

以前、別のブログで CoreDataでテーブルの件数を取得する記事を書いた。

Cocoa Touch の日々: CoreData - テーブルの件数を取得する

発行される SQLを調べてなかったので確認してみた。

SELECT COUNT(*) FROM ZSTYLETAG t0
※iPhoneSDK3.1.3にて


無駄なSQLは出ていないようだ(当然か)。

2010年5月28日金曜日

CoreData - SQLite の LIMIT

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

-[NSFetchRequest setFetchBatchSize:] で指定したサイズ値がフェッチ時に発行される SQLの LIMIT値となる。

例えば

[fetchRequest setFetchBatchSize:20];
とするとSQLはこうなる。
SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZMODIFIEDDATE, t0.ZFEE, t0.ZMEMO, t0.ZCREATEDDATE, t0.ZOPERATEDDATE, t0.ZCUSTOMER FROM ZKARTE t0 WHERE  t0.Z_PK IN  (?,?,?)  ORDER BY t0.ZOPERATEDDATE DESC LIMIT 20

3件ヒットするケースで、サイズを1とすると
[fetchRequest setFetchBatchSize:1];

こうなる。
sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZMODIFIEDDATE, t0.ZFEE, t0.ZMEMO, t0.ZCREATEDDATE, t0.ZOPERATEDDATE, t0.ZCUSTOMER FROM ZKARTE t0 WHERE  t0.Z_PK IN  (?)  ORDER BY t0.ZOPERATEDDATE DESC LIMIT 1

sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZMODIFIEDDATE, t0.ZFEE, t0.ZMEMO, t0.ZCREATEDDATE, t0.ZOPERATEDDATE, t0.ZCUSTOMER FROM ZKARTE t0 WHERE  t0.Z_PK IN  (?)  ORDER BY t0.ZOPERATEDDATE DESC LIMIT 1

sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZMODIFIEDDATE, t0.ZFEE, t0.ZMEMO, t0.ZCREATEDDATE, t0.ZOPERATEDDATE, t0.ZCUSTOMER FROM ZKARTE t0 WHERE  t0.Z_PK IN  (?)  ORDER BY t0.ZOPERATEDDATE DESC LIMIT 1

LIMIT 1 のSQLが件数分(3件)発行される。

※iPhone OS 3.1.3 用コードで確認

2010年5月27日木曜日

CoreData - 検索見本(NSManagedObjectを条件に検索)

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

NSPredicate を使う。
例えば、Customer(マスタ)<−−−>> Karte(ディティール)というモデルがあり、特定の Customer に紐づく Karteの一覧を取得する場合。


// Edit Predicade
NSPredicate* predicate =
  [NSPredicate predicateWithFormat:@"customer == %@", customer];
[request setPredicate:predicate];


オブジェクトが条件の場合でも上記のように等号条件として書ける。

SQLite のログはこう。
SELECT 0, t0.Z_PK FROM ZKARTE t0 WHERE  t0.ZCUSTOMER = ? ORDER BY t0.ZOPERATEDDATE DESC

SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZMODIFIEDDATE, t0.ZFEE, t0.ZMEMO, t0.ZCREATEDDATE, t0.ZOPERATEDDATE, t0.ZCUSTOMER FROM ZKARTE t0 WHERE  t0.Z_PK IN  (?,?,?)  ORDER BY t0.ZOPERATEDDATE DESC LIMIT 20

最初に Customer を条件にして合致する Karteの PK(Primary Key)を取得している。そして、取得した PK を SELECT ...IN( ) の条件として Karteのデータを得る。

# 合致する Karte が1万件あったら、IN (1万件の PK)となるのだろうか。
(追記)通常、LIMIT 制限するのでそんなことはおこらない。

※iPhone OS 3.1.3 用コードで確認

2010年5月26日水曜日

CoreData - マイグレーション

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

モデルの修正
CoreDataのマイグレーションを試してみた。通常モデルに修正を加えると、直前に作成したSQLiteDBは使えず実行時エラーが出る。この場合、SQLiteDBを一旦削除する必要がある。当然データの引き継ぎはできない。

エラーログ:








[16769:a0f] Error Domain=NSCocoaErrorDomain Code=134100 UserInfo=0x116760 "The managed object model version used to open the persistent store is incompatible with the one that was used to create the persistent store."
[16769:a0f] This NSPersistentStoreCoordinator has no persistent stores.  It cannot perform a save operation.


CoreDataでのデータマイグレーション
CoreDataにはマイグレーションの仕組みが用意されていて、これを利用することができる。

Core Data Model Versioning and Data Migration Programming Guide: Introduction to Core Data Model Versioning and Data Migration Programming Guide

属性値の追加など簡単な変更については自動マイグレーションが使えるようだ。

Core Data Model Versioning and Data Migration Programming Guide: Lightweight Migration

自動マイグレーションは下記が参考になる。
Core Dataの自動マイグレーション | hippos-lab::blog

試してみよう。



自動マイグレーション

1. モデルバージョンを追加する。
CoreDataRelationship_DataModel 2.xcdatamodel が追加された。



2. モデルを変更する。
今回は新しいバージョンの方に属性 published (NSData)を追加する。


3. Mapping Model を作成する。
名前は map-01-02.xcmappingmodel としてみた。
ソース(旧)とデスティネーション(新)のモデルをそれぞれ指定する。
4. Mapping Model ファイルを開く。
「差分を表示」を使うと新旧モデルの比較ができる。下図は published が追加されたことを表している。
5. 属性マッピングを設定する
published はオプションなのでそのままでも問題ないのだが、試しに created の値を使うようにしてみた。


6. マイグレーションコードを追加する。

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
 configuration:nil
   URL:url
       options:options
 error:&error];


options: にマイグレーション用オプションを追加する。

7. 現在のバージョンを設定する。
新バージョンを選択後にメニューから設定する。
チェックがつく。


8. 実行
エラーが出なくなった。SQLite の中をみてみる。
sqlite> .schema ZBLOGENTRY
CREATE TABLE ZBLOGENTRY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZPUBLISHED TIMESTAMP, ZCREATED TIMESTAMP, ZTITLE VARCHAR, ZCONTENT VARCHAR );
ZPUBLISHED が追加されている。

データも ZCREATEDの値がコピーされているのが確認できた。
sqlite> select * from ZBLOGENTRY;
1|2|5|296460106.418831|296460106.418831|iPad 5/28発売|かくかくしかじか
2|2|8|296460106.418047|296460106.418047|CoreData のリレーションしっぷについて|かくかくしかじか



飛ばしバージョンアップ (1→3)
さらに続けてバージョン3を作る。その上で、バージョン1のデータがどうなるかをみてみよう。

まずバージョン3のモデルを作る。
BlogEntryへ visible(BOOL)を追加してみた。

Mappin Model を作る。ソースはバージョン2、デスティネーションはバージョン3にした。
新属性 visible には YES (==1)を当てた。
実行してみよう。まずはバージョン2→3の動作をみる。
sqlite> .schema ZBLOGENTRY
CREATE TABLE ZBLOGENTRY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZVISIBLE INTEGER, ZPUBLISHED TIMESTAMP, ZCREATED TIMESTAMP, ZTITLE VARCHAR, ZCONTENT VARCHAR );



sqlite> select * from ZBLOGENTRY;
1|2|4|1|296460106.418831|296460106.418831|iPad 5/28発売|かくかくしかじか
2|2|6|1|296460106.418047|296460106.418047|CoreData のリレーションしっぷについて|かくかくしかじか
visibleが追加されているのが確認できた。

さて次はいよいよ1→3のマイグレーションを試みる。とっておいたバージョン1のSQLIteDBファイルをおいて、現在のモデルのバージョンを3として実行する。


結果はOK。

おお賢い。おそらく 1→2, 2→3 と自動的にマイグレーションを適用しているのだろう。


sqlite> .schema ZBLOGENTRY
CREATE TABLE ZBLOGENTRY ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZVISIBLE INTEGER, ZPUBLISHED TIMESTAMP, ZCREATED TIMESTAMP, ZTITLE VARCHAR, ZCONTENT VARCHAR );


sqlite> select * from ZBLOGENTRY;
1|2|4|1|296460106.418831|296460106.418831|iPad 5/28発売|かくかくしかじか
2|2|6|1|296460106.418047|296460106.418047|CoreData のリレーションしっぷについて|かくかくしかじか

気がついた点

バックアップファイルが自動生成される
SQLite のDBファイルがマイグレーション直前にコピーされ、バックアップがとられるようだ。


CoreDataRelationship.db
CoreDataRelationship~.db      ←バックアップ




NSInferMappingModelAutomaticallyOption
-[NSPersistentStoreCoordinator addPersistentStoreWithType:configuration:URL:options:error:] で渡すオプション NSInferMappingModelAutomaticallyOption は Mac OS v10.6 以上なので、v10.5 では使えない。

調べたところ iPhone は 3.0 以上で利用できるようだ。

2010年5月25日火曜日

CoreData - Xcodeでモデルクラスを自動生成する

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

Xcodeのモデルクラス自動生成機能を使った開発および運用方法など。

自動生成方法
モデルエディタでモデルを選択した後、メニューから「ファイル > 新規ファイル」を選ぶと管理クラスオブジェクトが選べるようになるのでこれを選択する。


生成されるクラス
下図のモデルのクラスを自動生成すると、関連(リレーションシップ)用のメソッドが追加される。



BlogEntry.h
@class Tag;
@class BlogComment;

@interface BlogEntry :  NSManagedObject  
{
}

@property (retain) NSDate * created;
@property (retain) NSString * title;
@property (retain) NSString * content;
@property (retain) NSSet* tags;
@property (retain) NSSet* comments;

@end

@interface BlogEntry (CoreDataGeneratedAccessors)
- (void)addTagsObject:(Tag *)value;
- (void)removeTagsObject:(Tag *)value;
- (void)addTags:(NSSet *)value;
- (void)removeTags:(NSSet *)value;

- (void)addCommentsObject:(BlogComment *)value;
- (void)removeCommentsObject:(BlogComment *)value;
- (void)addComments:(NSSet *)value;
- (void)removeComments:(NSSet *)value;

@end

BlogComment.h
@class BlogEntry;

@interface BlogComment :  NSManagedObject  
{
}

@property (retain) NSString * comment;
@property (retain) NSDate * created;
@property (retain) BlogEntry * entry;

@end

Tag.h
@class BlogEntry;

@interface Tag :  NSManagedObject  
{
}

@property (retain) NSString * name;
@property (retain) NSSet* entries;

@end

@interface Tag (CoreDataGeneratedAccessors)
- (void)addEntriesObject:(BlogEntry *)value;
- (void)removeEntriesObject:(BlogEntry *)value;
- (void)addEntries:(NSSet *)value;
- (void)removeEntries:(NSSet *)value;

@end


モデル変更時の対応
モデルの変更は、開発中のみならずシステム(アプリ)の運用・保守中にも必ず起こる。独自メソッドを自動生成クラスへ追加すると、その度にコードを追加しなければならなくなるので、これを避ける為にカテゴリを使う。

BlogEntryExtension.h

@interface BlogEntry (Extension) 
- (NSArray*)findAll;
@end






自動生成の自動化
mogenerator というソフトが公開されている。
nvie.com » Blog Archive » Automatically generate classes for your Core Data data model

これは未検証だが、使える内容ならビルドフェーズに組み込むと便利かもしれない。