cocoaのモデルデータ関連技術のメモ
指先を怪我したのでバンドエイドを貼ったら、trackPadが反応してくれない。モチベーションが落ちる。
ローカルへの保存方法
cocoaアプリのファイルの保存形式では大別して以下の4つがある。
| 保存形式 | 大量データ | 複雑なデータ | その他 | |
|---|---|---|---|---|
| 手書き | 気合い | 気合い | 気合い | |
| NSKeyedArchiver | 非公開 | r/wに時間がかかる | 重複処理あり | |
| CoreData | 非公開 | 一部だけ読み出せる | 重複処理あり | undo/redoサポート |
| plist | 公開 | r/wに時間がかかる | 重複処理なし |
この表だけを見ると、お手軽さや機能を考えればCoreData一択。
model-viewの同期方法
Cocoaアプリのモデルとviewの同期方法には主に以下の4つがある。
| 依存性 | code or nib | その他 | |
|---|---|---|---|
| 手書き | コード | ||
| cocoa binding | iOSで使えない | nib | |
| fetchResultController | CoreData前提 | コード | coreDataがiCloudStorageと相性が悪い |
| KVOとaction | コード |
CoreDataを使う場合は、FetchResultControllerとKVOを使って書くのがよさげ。 CoreDataを使わない場合は、ButtonなどはKVOで書いて、TableViewはデリゲートを使った手書きになりそう。
データの保存と違って、手法の混在できるので、適材適所で行うのが良い。
デバイス間のドキュメントの同期方法
調べている途中で気がついたのですが、CoreData Storage 又はCoreData in iCloudと呼ばれる技術はDeprecatedになりました。 CoreDataを使って、iCloud経由でドキュメントを同期できる夢の技術だったのですが、致命的なバグでもあったのでしょう。
で、デバイス間のドキュメント同期技術は以下の2つです。
- iCloud document storage
file単位に基づく同期。DropBoxのApple版といった所。PowerPointやWordのようなタイプの書類ベースのアプリとは相性が良いかもしれない。
変更部分だけのマージが出来ないので、巨大なバイナルファイルを丸ごと入れ替えになる。
- CloudKit storage
クラウドにデータを置くことが主眼。グラウドにあるデータが主となる。JSからの呼び出しをサポートしているので、webアプリを作る場合は必須。
ネットに繋がっていないと使えない欠点がある。FileMakerやEverNoteのようなパーソナルデータベース型のアプリとは相性が良いかもしれない。
ローカルキャッシュをCoreDataに、ネットに繋がっている時にCloudKitで同期を行うと、上記の欠点を解消できる。
デバイス間の操作状態同期方法
同じユーザーアカウントの別デバイスに操作状態を引き継げる。
NSUserActivityを設定するらしい。使ったことないけど。
NSOrderedSetの感想
NSOrderedSetが使えそうで使えない。
CoreDataでは、NSOrderedSetをサポートしている。しかし、NSArrayControllerがOrdersSetに対応していない。
bindingでNSTablbeViewの中に順列を使うには、NSSet & NSSortOrderの組み合わせで表現する必要がある。
bindingでなくNSFetchResultControllerを使用しても良い。
CoreData中のNSOrderedSetはCoreDataの思想とあまり相性が良くないのかもしれない。
bindingでのサポートだけでなく、CoreDataInCloudでもサポートが無かった。
地雷を踏み抜かないように、SortOrderを使った昔ながらの手法を使用した方が良い。
MacOSX用アプリ”CardBook.app”をビルドする
ノートテーカー系のアプリを作成しようと思い、参考になりそうなソースコードを漁っていると良さげなアプリをまた見つけた。
CardBook.app http://www.paullynch.org/macosx/cardbook/ https://github.com/pauldlynch/CardBook
細かい実装を丁寧にこなしている印象なメモアプリ。
修正
Copy&PasteやDrag&Drop時に例外を吐いたの少しだけ修正しました。 原因はNSArchiverの書き出しが現在のMacOSXではサポートされていないため。対応として、NSArchiverをNSKeyedArchiverに、NSUnarchiverをNSKeyedUnarchiverに書き換える以外は何もしていない。
修正箇所はファイルMyDocument.mの以下の2つのメソッドだけ。
[MyDocument copyRows:toPasteboard:]
- (void)copyRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard {
NSArray *array = [self cardsForRows:rows];
// NSArchiverをNSKeyedArchiverに置き換え
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];// <-- ここ
NSMutableAttributedString *aString = [[NSMutableAttributedString alloc] initWithString:@""];
NSEnumerator *e = [array objectEnumerator];
Card *card;
while (card = [e nextObject]) {
[aString appendAttributedString:[card card]];
}
[pboard declareTypes:[self pboardTypes] owner:self];
[pboard setData:data forType:CardPboardType];
[pboard setString:[aString string] forType:NSStringPboardType];
[pboard setData:[aString RTFDFromRange:NSMakeRange(0, [aString length]) documentAttributes:nil] forType:NSRTFDPboardType];
[pboard setData:[aString RTFFromRange:NSMakeRange(0, [aString length]) documentAttributes:nil] forType:NSRTFPboardType];
[aString release];
}
[MyDocument addCardFromPboard:atRow:]
- (void)addCardFromPboard:(NSPasteboard *)pboard atRow:(NSInteger)row {
Card *card;
NSData *data;
NSString *type;
type = [pboard availableTypeFromArray:[self pboardTypes]];
if ([type isEqual:CardPboardType]) {
NSEnumerator *e;
NSArray *cardArray;
data = [pboard dataForType:CardPboardType];
// NSUnarchiverをNSKeyedUnarchiverに置き換え
cardArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];//<--ここ
e = [cardArray objectEnumerator];
while (card = [e nextObject])
[self addCard:card atRow:row];
}
if ([type isEqual:NSRTFDPboardType]) {
data = [pboard dataForType:NSRTFDPboardType];
card = [Card card];
[card setCardRTFD:data];
[self addCard:card atRow:row];
}
if ([type isEqual:NSRTFPboardType]) {
data = [pboard dataForType:NSRTFPboardType];
card = [Card card];
[card setCardRTF:data];
[self addCard:card atRow:row];
}
if ([type isEqual:NSStringPboardType]) {
NSString *pboardString = [pboard stringForType:NSStringPboardType];
card = [Card card];
[card setCardString:pboardString];
[self addCard:card atRow:row];
}
}
修正したソースコードはここ。( CardBook-master.zip )
ソースコードの特徴
作成された年代的に以下の技術は使用されていない。
- cocoa binding
- core data
- restore state
- プロパティ
- ARC
- NSViewController
素直に、コードで書かれている。window位置が再生されるのは、保存ファイルに位置を書き込んでいるから。 データの保存は、伝統的なNSKeyedArchiverを使用している。
印刷時にはCardsViewクラスを使ってA4の紙に複数のカードを纏めて印刷可能にしている。最近印刷を前提としていないアプリばかりなので、これは時代的なものなのかもしれない。
他にも、AppleScript用のリソースやローカライズリソースなども存在しており、丁寧に作られているように見える。
UIの特徴
NSTableViewとNSTextEditViewの組み合わせで特に特徴的なものはない。
しかし、Wiki文法やmarkDownを使うことなく、RTFDな修飾を直接使う方がフツーに便利かもと再考させられた。
カード間リンクはない。 “フォーカス”機能で一部のカードだけをリストアップする機能がある。
標準のメモアプリの使用感が一番近いかもしれない。
GNUstep用アプリ”MyWiki.app”をビルドする
ノートテーカー系のアプリを作成しようと思い、参考になりそうなソースコードを漁っていると良さげなアプリを見つけた。
MyWiki.app http://wiki.gnustep.org/index.php/MyWiki.app
Objective-Cで書かれた、サーバーなしのDesktop用のwikiアプリ。
残念ながら、十年以上前のコードなのでそのままではビルドが出来ないので、手直ししてビルド。 修正したソースコードはここ。( MyWiki_test.zip )
カレンダーViewとページ作成日を利用した日誌ページの作成機能が面白い。作成日でまとめるために自動で日誌が出来上がる。
コードを読んでいて面白いのは、NSTextViewの本文中にコラムを作る手法が良くできている。 NSTextViewにNSTextAttachmentを使って、アイコンと文字列を表示させている。
自作ノートテーカーアプリには、文書中に”注意”や”意見”を入れたいと思っていたのでこの機能はパクりたい。
ユーザーとしての使い方は文字列をnoteタグで囲むだけでコラムができるのはお手軽で良い。
問題はDrag&Dropやコピペがうまくゆかない。NSTextAttachmentをコピーするときに、NSTextAttachmentの内容を書き出せば可能かもしれない。
PlantUMLのsalt言語
以前、オブジェクトグラフを書くのにdot言語を使用した( xibファイルの中のオブジェクト図 )。
この時に「テキストデータからGUI設計用の図を生成するDSLはないものか?」と思っていた。 ググると意外な場所で見つかった。
有名なPlantUMLの中のsalt言語がそれ。(http://plantuml.com/salt)
@startuml
salt
{+
. |<b>CurrencyConverter|*
--- | --- | ---
.|{
. | Doller: | "Input" |*
. | rate: | "input" |*
. | Yen: | output |*
. | [Reset] | [Convert] |*
}|*
}
@enduml
テキストファイル、mainWindow.puに以上のように書いて、
$ java -jar path/to/plantuml.jar -p -tpng < mainWindow.pu > mainWindow.png
と実行すると、
こんな画像が得られる。
メニューバーなども作ったり、makefileなども作って可能性を探った。 資料はここ。( plantUML-salt.zip )
結論:webアプリケーション用に設計されたようで、私の用途には表現が弱すぎる。スクリーンショットか手書きの方が良さげ。