(前回)Cocoaの日々: メモリ対決:NSTableView+カスタムセル vs NSCollectionView
前回の結果を受けてユーザインタフェイスには NSTableView+カスタムセルの組み合わせを使うことにする。今回は NSTableView+カスタムセルと CoreData の組み合わせについて検証する。
NSTableView+カスタムセルと CoreDataの組み合わせ
※ここでいうカスタムセルとは、1つのセルの中に複数の情報を表示する特別なセルを意味する。
通常のセルを使う場合 NSTableView と CoreData の相性は非常に良くて、連結はほとんど Bindings設定だけで済む。ただカスタムセルを使う場合は少し工夫が必要となる。前回までの検証では CoreData を使わないケースでカスタムセルとデータをバインドしていた。
Cocoaの日々: NSTableView にカスタムビューを表示する (5)カスタムセルへ bindings経由でモデルオブジェクトを渡す
しかし同じ方法が CoreData の場合は使えない。理由は NSTableColumn がセルへデータを渡す時に使うメソッドが NSCopying プロトコルへの準拠を要求する為。
- (void)setObjectValue:(id <NSCopying>)object
Mac Dev Center: NSCell Class Reference
CoreData を使う場合、データ1件を NSManagedObject として扱うが、NSManagedObject 自体は NSCopying プロトコルに準拠していない。また推奨もされていないようだ。
Mac Dev Center: Core Data Programming Guide: Managed Object Accessor Methods
仮にサブクラス化して NSCopying プロトコルを実装したとしても、今度は仲介する NSArrayController で問題が起こる。Interface Builder を使った設定ではデータは NSManagedObject固定でサブクラスを指定することができない(コードで書けばあるいはできるかもしれないが不明)。
さてどうするか。
NSManagedObjectID を使う
いろいろ考えたがうまい方法が思いつかない。そこでネットを調べてみると以前紹介した StackOverflow の記事にその答えが載っていた。
Core Data, NSTableColumn bindings and custom NSCell - Stack Overflow
紹介されていたのは NSManagedObject をバインドするのではなく、その ID である NSManagedObjectID をカスタムセルとバインドする方法。質問者も私と同じ問題に悩んでいたようだ。
Mac Dev Center: NSManagedObjectID Class Reference
おー、なるほど。その手があったか。NSManagedObjectID が手に入れば後は -[NSManagedObjectContext objectWithID:] で目的の NSManagedObject を取り出して表示できる。
Mac Dev Center: NSManagedObjectContext Class Reference
NSManageObjectID は NSCopying プロトコル準拠なので先ほどの問題もクリア。
さっそく試してみよう。
サンプルプログラム
新規にプロジェクト(CustomCellWithCoreData)を作り実装していく。主要クラスのコードは前回までのサンプルからコピーした。
データモデルを定義する。内容はこれまでの検証で使ってきた Homepage と同じ。
InterfaceBuilderを立ち上げ、NSTableViewを配置する。カラムは1列としておく。
NSArrayController を一つ設置する。このクラスがカスタムセルとCoreDataの仲介を行う。
NSArrayController の Attributes はこんな感じ。
Mode を "Entity" に Entity Name に 先ほど定義したデータモデルのエンティティ名 "Homepage" を指定する。
次の図は NSTableColumn の bindings設定。
Controller Key に "arrangedObjects"、Model Key Path に "objectID" を指定する。こうすると -[NSManagedObject objectID] の結果(すなわち NSManagedObjectID)がカスタムセルへ渡される。
UI の調整が済んだら次はコーディング。まずは制御部分から。
CustomCellWithCoredata_AppDelegate.m
- (void)awakeFromNib
{ NSManagedObjectContext* moc = [self managedObjectContext]; [self createTestRecord]; [array_controller setManagedObjectContext:moc]; CustomCell* cell = [[[CustomCell alloc] init] autorelease]; [cell setEditable:NO]; cell.managedObjectContext = moc; [table_column setDataCell:cell]; }
最初に全体のオブジェクト間の調整を行う。
・NSArrayController と NSManagedObjectContext の紐づけ
・NSTableColumn と CustomCell の紐付け
・CustomCell と NSmanagedObjectContext の紐付け
-[createTestRecord] ではテストデータを10件作成している(結果は CoreData を通じて SQLiteデータベースへ格納される)。
次にカスタムセルの描画コード。ほとんどが以前のコードの流用。Homepageインスタンスの取得箇所だけ今回手を入れてある。
CustomCell.m
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView { NSManagedObjectID* moid = (NSManagedObjectID*)[self objectValue]; Homepage* homepage = (Homepage*)[self.managedObjectContext objectWithID:moid]; NSImage* image = [[[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:homepage.image]] autorelease]; [controlView lockFocus]; NSPoint p1 = cellFrame.origin; p1.x += 5; p1.y += 5 + [image size].height; [image compositeToPoint:p1 operation:NSCompositeSourceOver]; NSPoint p2 = cellFrame.origin; p2.x += 100; p2.y += 20; NSDictionary* attrs = [NSDictionary dictionary]; [homepage.title drawAtPoint:p2 withAttributes:attrs]; [controlView unlockFocus]; }NSManagedObjectID があれば NSManagedObjectContext を通じて Homepage が簡単に取得できる。
実行
さて実行してみよう。
出た。特に問題なし。
ソースコード
github からどうぞ
CustomCellWithCoredata at 20091207 from xcatsan's SampleCode - GitHub
- - - -
機能的には問題なさそうだ。となると次はメモリの利用状況か(続く)。