NSTreeControllerで使用するObjectの変換についてのメモ
NSTreeControllerでnodeを示すのに使用されるObject
- NSTreeNode
- NSTreeControllerで木構造のノードを管理しやすくするためのクラス。このクラスのインスタンスから、自分自身のNSIndexPathや、NSManagedObjectへの参照を得ることができる。 親ノード、小ノードを手繰ることが出来る。
- NSIndexPath
- ノードの位置を数字の列で指定するクラス。各ノードは、親のオブジェクトのNSArrayに入っている。 この、NSArrayのindexの値を複数持つことで、fileSystemのパスのように目的のノードの位置を指定することができる。 2/22追記:選択範囲を”状態の保存”で保存する場合は、このindexPathを使用するのが望ましい。NSTreeNodeやNSManagedObjectは保存ファイルやポインターに依存しているので、識別子の変換作業が追加で必要になる。
- NSManagedObject
- NSTreeControllerが参照するモデルとしてCoraDateを使用する場合に、ノードのデータ本体としてこのクラスのサブクラスが使用される。NSTreeNodeのrepresentedObjectに設定される。
- row
- NSTreeControllerがNSTableViewやNSOutlineViewに結び付けられている時に、表示中の0から始まる行番号を示す。表示上の値であるため、兄弟ノードの子ノードが表示の為に開閉されると、子ノード分増減する。
上記のObjectの相互変換
NSTreeNodeから各種Objectへの変換
もっとも簡単に変換できる。気をつけることは、rowはviewにしか存在しない概念なので、NSOutlineViewで変換するしかないことぐらい。
| 型 | 方法 |
|---|---|
| NSIndexPath | NSTreeNode.indexPath |
| NSManagedObject | NSTreeNode.representedObject |
| row | [NSOutlineView rowForItem:NSTreeNode] |
NSIndexPathから各種Objectへの変換
一度NSTreeNodeに変換してからそれぞれの型に変換する。
| 型 | 方法 |
|---|---|
| NSTreeNode | [NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath] |
| NSManagedObject | [NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath].representedObject |
| row | [NSOutlineView rowForItem:[NSTreeController.arrangedObjects descendantNodeAtIndexPath:NSIndexPath]] |
NSManagedObjectから各種Objectへの変換
NSTreeController.arrangedObjectsを使って、総なめする以外には方法はない。
コーディングするときは、NSManagedObjectは中間表現としては使ってはならない。NSIndexPathかNSTreeNodeを使用すること。
rowから各種Objectへの変換
rowはviewにしか存在しない概念なので、NSOutlineViewでNSTreeNodeへ変換する。 その後、それぞれの型に変換する。
| 型 | 方法 |
|---|---|
| NSIndexPath | [NSOutlineView itemAtRow:row].indexPath |
| NSTreeNode | [NSOutlineView itemAtRow:row] |
| NSManagedObject | [NSOutlineView itemAtRow:row].representedObject |
なお、キャストなどは省略した。上記のコードはコピペでは動かない。
sphinxのadmonition用にcssを書いたの続き
前回のアイコンはサイズも適当に作成していた。画像の余白を削り、サイズも統一した。 さらに、CSSを変更して、タイトル部分をboldにして、それぞれの要素のマージンを合わせた。
Caution
caution用のディレクティブはこう。old mac風のアイコンで区別できるようにしてみてます。なかなかにそれっぽくできた。
Danger
危険(?)のディレクティブをかくとこんな感じ。前回は爆弾マークだったのだが、テロ推奨マークのようであまりいい感じがしなかったので、ドクロマークに変更した。
Note
ノート用のディレクティブはこう。この”Note”や”Danger”の文字を消すには、コラムディレクティブ(http://sphinx-users.jp/cookbook/columndirective/index.html)を使う必要があるみたい。CSSのfloat属性とPaddingをうまく使って、”Note”や”Danger”の文字をアイコンの下へつけるようにした。
Tip
tipのディレクティブをかくとこんな感じ。本文の上辺のpaddingもアイコンの上辺と合わせたつもり。
Warning
警告用のディレクティブをかくとこんな感じ。種類によって背景色も変えた方が良いかもと思って色々と試した。が、色をつけると以外に下品になる。色についてはAppleHelpFileを作るときにもう少し考えてから行うことにする。
cssの実装方法は、htmlのソースとcssのソースを見ればすぐにわかると思うので特に記述しない。
sphinxのadmonition用にcssを書いた
適当にアイコンを作り、コラム風に囲ってみた。
cssの実装方法は、htmlのソースとcssのソースを見ればすぐにわかると思うので特に記述しない。
(注意) CSSを変更して、再ビルドしたため上記は、画像ファイルに差し替えた。
NSAtomicStoreサブクラスの作成のメモ
CoreDataがサポートしてい保存形式
CoreDataはデータ保存形式として予め以下の4つの形式をサポートしている。
| 指定文字列 | description | |
|---|---|---|
| SQLite | NSSQLiteStoreType | SQLiteに保存する |
| バイナリ | NSBinaryStoreType | バイナリ形式でfileに保存する |
| XML | NSXMLStoreType | XML形式でfileに保存する。iOSにはない。 |
| in Mmemory | NSInMemoryStoreType | メモリだけ使用し保存しない。 |
これらのStoreTypeを目的に応じて使い分けることができる。そして目的に合わない場合に、自作することも可能である。
クラス階層
ここでは、NSBinaryStoreTypeやNSXMLStoreTypeと同じatomicな読み書きを行い、独自のファイ形式をサポートするStoreタイプの作成方法を説明する。
まずは、既存のStoreTypeがどのクラス階層に属しているかを示す。 残念ながら、既存のStoreTypeのクラスはドキュメント化されていないので指定用文字列をクラス名の代わりに使用している。
クラスNSPersistentStoreは、CoreDataで使用する永続化方式の抽象クラスです。このクラスのサブクラスを書くことで、CoreDataのデータを任意の媒体に保存できるようになる。 デザインパターンで言うところの、テンプレートパタンやストラテジーパタンで設計されている。
NSIncrementalStoreは、部分的に保存することができるStoreの抽象クラスです。既存のクラスではNSSQLiteStoreTypeがこのクラスを継承している。他のデータベースやNoSQL的なものをサポートしたい場合は、このクラスを継承して作ることになる。
NSAtomicStoreは、データを保存するときに一括で保存する必要があるStoreの抽象クラスです。既存のクラスではNSBinaryStoreTypeとNSXMLStoreTypeがこのクラスを継承している。
このNSAtomicStoreのサブクラスを作成することで、CoreDataの保存形式に独自のファイルフォーマットを導入する。
実行時のオブジェクトグラフとNSAtomicStoreCacheNode
NSAtomicStoreのサブクラスの内部では、NSManagedObjectのデータをNSAtomicStoreCacheNodeに対1で転写して使用する。 NSAtomicStoreCacheNodeはサブクラス化しなくとも問題なく使える。
NSAtomicStoreCacheNodeとNSManagedObjectとNSAtomicStoreの関係は、以下の3点だけ覚えておけば問題ないはず。
- NSAtomicStoreCacheNodeはNSAtomicStoreに所有される。
- NSAtomicStoreCacheNodeはNSManagedObjectと1:1で関係する。
- NSAtomicStoreCacheNodeを区別する識別子を割り当てる。
図で示すと、以下のようになる。
NSAtomicStoreをサブクラス化するには
NSAtomicStoreをサブクラス化するにあたっての、上書きすべきメソッドは大別して2つのグループに分かれる。
メタデータの管理系のメソッド
- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator*)coordinator
configurationName:(NSString*)configurationName
URL:(NSURL*)anUrl
options:(NSDictionary *)options
+ (NSDictionary *)metadataForPersistentStoreWithURL:(NSURL*) url
error:(NSError*__autoreleasing*) error
+ (BOOL) setMetadata:(NSDictionary*) metadata
forPersistentStoreWithURL:(NSURL*) url
error:(NSError*__autoreleasing*) error
- (NSString*)type
- (NSString*)identifier
- initWithPersistentStoreCoordinator…は、初期化メソッド。ここで、メタデータを読み込むか初期化する必要がある。
- metadataForPersistentStoreWithURL…は、メタデータの読み込みメソッド。指定されたURLからメタデータを読み込む。
- setMetadata…は、メタデータの書き込みメソッド。指定されたURLからメタデータを書き込む。
- typeは、クラスの識別子を返します。この識別子によって、CoreDataは新たに作成したNSAtomicStoreのサブクラスを認識できます。[NSPersistentStoreCoordinator registerStoreClass:forStoreType:]に渡す文字列。NSSQLiteStoreTypeやNSXMLStoreTypeと同じ目的の文字列。
- identifierはインスタンスの識別子です。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。
NSAtomicStoreCacheNodeの管理系のメソッド
- (BOOL)load:(NSError*__autoreleasing*) error
- (BOOL)save:(NSError*__autoreleasing*) error
- (NSAtomicStoreCacheNode*)newCacheNodeForManagedObject:(NSManagedObject*) inManagedObject
- (void)updateCacheNode:(NSAtomicStoreCacheNode*)node fromManagedObject:(NSManagedObject*)managedObject
- (id)newReferenceObjectForManagedObject:(NSManagedObject*) managedObject
- load…はファイルからデータを読み込ませる。
- save…はファイルへ保存させる。
- newCacheNodeForManagedObject..は与えられたNSManagedObjectに対応するNSAtomicStoreCacheNodeを作成する
- updateCacheNode…はnodeへmanagedObjectの内容を反映させる。
- newReferenceObjectForManagedObjectはNSManagedObjectの識別子を生成する。保存して再生した場合は、また同じ値を返す必要があります。そーでないと識別子として動作しない。
ファイルフォーマットの設計
NSAtomicStoreのサブクラスを作成して、CoreDataを任意のファイルフォーマットに対応させるにはいくつかの条件がある。
- ファイルを区別するためのNSStoreUUIDKeyの値の保存が必要。メソッドidentifierがその値を返す必要がある。
- レコードに相当するNSManagedObjectを区別するための、リファレンスの保存が必要。
- テーブルに相当する値を何らかの方法で識別する必要がある。
ここで、plist形式やxml形式などの汎用ファイルフォーマット形式であれば対応可能。既存のファイルフォーマットに上記の条件を合わせるには何らかの工夫が必要になる。
全てのCoreDateのファイルを保存するのでなければ、それなりに簡単にできる。
ここでは、以前作成したToDoアプリ元にcsv形式での読み書きをサポートさせてみる。
以下のようにした。
- 1行目はメタファイル行。metaDataのUUIDの値
- 2行目以降は通常のcsvファイル
- ToDoアプリのみサポート。テーブル名は保存しない。
- 項目は、done, memo, sortOrder, 作成日, uuid(レコードのuuid値)
具体的にはこんな感じ。
#D35CF030-1EC1-4667-9666-70C8247A1010
1, insertCurrent, 2, 2016-09-17 18:15:00 +0000, E899556C-BE1A-42A3-A634-F8411915C05F
0, insertCurrent, 5, 2016-09-17 18:15:00 +0000, 803054FC-C497-4DD7-A078-CF592B994EB4
1, insertCurrent, 3, 2016-09-17 18:15:00 +0000, 569485BA-A1E2-4E5B-88B5-6D5DB2C8AB8F
0, insertCurrent, 1, 2016-09-17 18:15:00 +0000, 65A95220-A09F-4000-9F3C-EC8448D5DCB7
0, addLast, 0, 2016-09-17 18:15:00 +0000, F7C54A12-8CA6-4A4A-9D45-1A92E23922F9
1, insertCurrent, 4, 2016-09-17 18:15:00 +0000, 5CE1C138-32A3-4CE8-9917-84C941011C09
CoreDataへの登録とinfo.plistへの追加
NSAtomicStoreのサブクラスを使用するには、NSPersistentStoreCoordinatorに登録する必要がある。 適当な場所に以下のコードを書いて、登録する。applicationDidFinishLaunchingが良いと思う。
[NSPersistentStoreCoordinator registerStoreClass:[TDLAtomicStore class]
forStoreType:TDLAtomicStoreType];
同じく、NSPersistentDocumentと一緒に使う場合は、info.plsitにDocument typeを追加する。
<key>CFBundleDocumentTypes</key>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>csv</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>text/csv</string>
</array>
<key>CFBundleTypeName</key>
<string>CSV</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>TDLDocument</string>
<key>NSPersistentStoreTypeKey</key>
<string>TDLAtomicStoreType</string>
</dict>
と登録しておけばOK。これで、.csvの拡張子のファイルを開けるようになるし、”save as..”でTDLAtomicStoreTypeの保存方式が使えるようになる。
ソースコードの解説
めんどくさくなってきたので、Cocoa勉強会Matudoで説明します。https://connpass.com/event/46754/
ファイルTDLCSVAtomicStore.h.mにまとまってます。
ソースコードはここ。( ModernToDoList_atomicStore.zip )