2009年7月8日

低速列挙?

配列という概念に必要以上に気を取られていたけど、元々は、

・XMLデータが格納されているNSXMLDocument *documentがあり、
・それをnodesForXPathでノードにしてやったデータがNSArray *gameArrayに入る。

のだから、gameArrayはつまり「NSXMLNode形式の配列」だ。ということはfor (NSArray *arr in gameArray)じゃなくて、もう一歩踏み込んでfor (NSXMLNode *gaNode in gameArray)と書けば良いのだった。これで高速列挙の中で、個別のゲームに関するXMLNodeが一個ずつ取得できる。

あとは一番大きな括りであるの「ほげほげ」を取り出すために、initWithXMLString[gaNode XMLString]を指定してNSXMLElementを作ってやる。そのXMLElementにたいしてattributesを与える事によって、ほげほげ部分が配列で返ってくるので、これをNSEnumeratorで順番にもらってもいいんだけど、今回は最初の配列だけ欲しいので、簡単にobjectAtIndex:0で取得する。

というような流れは、一年ちょっと前に同じ事をしていたんだけど、ここにきてもう少し理解できるようになっている事に気がついた。少しずつ視野が広がっている感じで面白い。しかし処理が重すぎるのはどうしたことか。NSTaskでリストのXMLを吐き出させ、それをNSXMLDocumentに格納し、パースして、テーブルに表示するまで4分ほどかかる。これでは低速列挙になってしまうので、ここも改善していかなければ。

2009年7月7日

高速列挙

日本語だと何やら凄い雰囲気になってしまうけれど、今までfor文を使ってループを回していた処理が、この高速列挙という書き方で速くなるらしい。でもちょっと使ってみただけでは、どこがどのくらい速くなったのか見極めにくい。

あと高速列挙は、例えば配列myArrayがあるとして、配列の中身を文字列strとして取り出して処理していくような場合、これまでだと、

for (i=0; i < [myArray count]; i++)
{
 処理(配列を取り出すには[myArray objectAtIndex:i]みたいな)
}

なんてやっていた処理を、

for (NSString *str in myArray)
{
 処理(配列を取り出すにはstrだけ書けばよい)
}

というように書くけど、必ずしもNSStringが欲しい訳じゃなく、処理の中では配列を「配列そのもの」として使いたい場合もある。配列からノードを取り出したいときとか。その場合は、

for (NSArray *arr in myArray)

と書けばいいのかな。ちょっと試してみよう。これくらい、試してから書けば良いんだけど、ちょっと備忘録。

2009年7月1日

やっと絞り込めた



かなり初期の段階から、実装したくもサッパリできなかった「インクリメンタルサーチ」が、やっと動いた。Core Dataの領域に少し踏み込んで、そこからヒントというか答えを見つけた感じ。なかなか嬉しい。

NSArrayControllerを使ったバインディングで、テーブルにリストを表示する、というのはできていたので、あとは検索フィールドをバインディングでどうにかしてやるのだろう、というところまでは推測していたんだけど、これまでのバインディングと同じ感覚で捉えていた。つまり何かしらキーを作って、なんだったらアクセッサメソッドでコントロールするような。でも今回は違う。

まず検索フィールドのバインディング設定で、PredicateをNSArrayControllerのインスタンスである「Array Controller」にバインドする。このArray Controllerはテーブルへの表示にも使っているもの。そしてModel Key Pathにキーを設定して、アクセッサメソッド内でNSPredicateを作ってやるのかと思いきや、Model Key Pathは空白。検索条件を直接「Predicate Format」の部分に書いてやる。例えば、

sGameTitle.description contains[c] $value

こう書くと、NSArrayControllerに「sGameTitle」というキーで登録した辞書オブジェクトに、検索フィールドに入力された文字列が含まれているかどうか、というようなことになる(なんだか日本語が長くて変だ)。そして今回だとゲームタイトルだけじゃなくて、発売年とか製造元まで検索対象に含めたいので、そういった複数の条件を指定する場合は、上記の条件をカッコで括ってやり、間を「or」でつなぐ。つまり、

(sGameTitle.description contains[c] $value) or (sManufacturer.description contains[c] $value)

という感じだ。どんどんPredicate Formatの入力部分が長くなっていくけど気にしない。こうやって書いてやれば、検索フィールドに文字を入力していく度に、どんどんリストが絞り込まれて表示されていく。やっとうまく動いたので、ここからどんどん発展させていこうと思う。

2009年6月19日

日本語でも表示



前回の変更点「タイトルを日本語でも表示できるようにする」がひとまず動いた。ただ中身はかなり冗長な処理になっている。まず日本語リストは「mamelistjpな部屋」で配布されているものを使用。中身は一行ごとに、

「ロムセット名」「日本語ゲーム名」「日本語かな読み」「メーカー名」

となっていて、各項目はタブ区切りになっている。つまり行としては8000行以上ある。これをまずテキストデータとして読み込み、改行ごとにバラして配列に入れる(これを配列Aとする)。そのついでに、そこからロムセット名だけ抽出した配列(これを配列Bとする)を別に作っておく。

配列BはNSArrayのindexOfObject:を使って、-listxmlから吐き出したロムセット名で検索してやる。同じ名前のロムセットが見つかったら配列の番号が返ってくるので、配列Aにその番号を指定して一行吸い取り、さらにタブごとにばらして必要な要素を取得する。「ロムセット名」と「メーカー名」はすでに英語で同じものがあるので、必要なのは「日本語ゲーム名」と「日本語かな読み」の二つ。

indexOfObject:の使い方は、例えばこんな感じ。

unsigned int i = [array indexOfObject:@"hoge"];

配列arrayの中にhogeがあれば、その配列番号を返してくれる仕組み。なければNSNotFoundという定数が返ってくる。indexOfObject:は文字の比較にisEqual:を使うため、配列Aのように「個々の配列はタブで区切られた文字列が四つある」ような場合は使えない(のか?)。そのため検索用に配列Bを用意したけど、何かもう少し良い方法が思いついたら変更するかもしれない。

そして、まずボタンを押してNSTaskでlame -listxmlの処理が完了するのに25秒。結果のゲームリストが画面に表示されるまでに、そこからさらに72秒もかかる。ちょっとかかり過ぎかも。

2009年6月16日

もう一度作ってみる

これまで培ってきた、というほど大げさではないにしろ、何となくいろいろやってきたことを基にして、もう一度SDLMAMEのランチャーを再構築してみることにする。前に作ったやつは適当に動いているけど、そこからの変更点としては、

・テーブルの表示にバインディングを使う
・絞り込み表示ができるように
・タイトルを日本語でも表示できるようにする
・NSTaskを別のタスクで回す

今のところこんな感じ。現在は指定された場所にあるSDLMAMEからリスト(-listxmlで出力されるやつ)を吐き出してやって、それをパースして、バインディングを使ってテーブルに表示する、という部分ができている。ここから今度は、日本語タイトルファイル(これは熱意のある人が作って配布しているものを使わせてもらう)を取り込んで、切り替えて表示できるようにする。

それにしても開発用のPowerMac G4 (1GHz)だと、-listxmlしてテーブルに表示されるまでに1分くらいかかる。デバッグモードだからというのもあるだろうけど、もうちょい何とかしたいところ。でもまあ、まずはちゃんと動くものを作るのが先か。

2009年6月1日

やっとテーブルと組み合わせ

処理はおおむね以下。

1.ファイル、もしくはフォルダが一個、または複数個放り込まれる
2.フォルダだった場合はサブフォルダの中身を取得する
3.ファイル名の末尾がaiffやwavだったらテーブルに追加する

まずはこれを実現すべく、今までの知識を総動員(というほど多くないけど)してサンプルプログラムを組み立てる。以前の「動かない見本」でいうところの4番「ドラッグ&ドロップしたあと」の部分を修正した。

- (BOOL)performDragOperation:(id <nsdragginginfo>)sender
{
 // 複数のファイルをドロップしている可能性もあるので、NSArrayに格納する必要がある
 NSArray *fileNames = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];

 // 投げられたファイルの判別を外部クラスに任せてみる。クラスメソッドの呼び出しということ?
 NSMutableArray *mArray = [NSMutableArray arrayWithArray:[myProcess pdoDiscernArray:fileNames]];

 // それをNSArrayControllerに追加
 if (mArray)
 [myArrCtrlr addObjects:mArray];

 return YES;
}


で、Xcodeで新規クラス"myProcess"(NSObjectを継承したもの)を作り、そこに+ (NSMutableArray *)pdoDiscernArray:(NSArray *)ddItemというメソッドを用意して、放り込まれたファイルやフォルダの情報をそのまま(NSArrayのまま)渡して処理させる。これは前回の、フォルダかどうか判断するメソッドを使う。クラスのメソッドを呼び出すので+を使う……のかな。相変わらずこういう部分を曖昧なまま進めていくので、突然壁に当たったり袋小路に入り込んだりするのだろう。いろいろ試してから解説書を読めば理解も深まるだろう、などと考えている。配布するならキッチリ理解しないといけないけど。

そうしてメソッド内でファイル名とキー(今回は@"tableContents"という名前)が対になったNSDictionaryを作り、それをNSMutableArrayに一つずつ追加していき、最終的に返り値として戻してやる。戻ってきた配列をNSArrayControllerのインスタンスとして用意したmyArrCtrlrに追加してやる。なおキーである@"tableContents"はNSTableColumnにバインドしてある。

これでひとまず最低限の動きが実現できた。次はこれを使って、実際にファイルに対して何かしらの処理をしていこうと思う。でもその前に、もう少し足場周りを固める必要があるような気もする。

2009年5月22日

ファイル or フォルダ



ウィンドウにファイルやフォルダをドロップすると、そのパスを表示する。その際、フォルダだったら中にあるファイルも一緒に表示させたい。ということは、まずドロップされたものがファイルなのかフォルダなのか判断する必要が出てくる。

ここで使ったのはNSFileManagerのfileExistsAtPath:isDirectory:というメソッド。簡単な使い方はこんな感じ。

NSFileManager *myFileMng = [NSFileManager defaultManager];
BOOL isFolder;
NSString *path = [NSString stringWithString:@"ここにファイルのパスが入る"];

if ([myFileMng fileExistsAtPath:path isDirectory:&isFolder])
{
 // ファイルのパスは正しいという前提なので、フォルダかどうかだけ見る。
 if (isFolder)
 {
  // フォルダだった場合の処理
 }
 else
 {
  // ファイルだった場合の処理
 }
}


まずデフォルトのファイルマネージャを作ってやり、そこにファイルのパスを入れてやってチェックする。ついでにフォルダかどうかの判断もしてくれる(こっちが本題だ)ので、フォルダだった場合は中にあるファイルを探しても良い。画像は、とりあえず判断した結果をファイルパスの頭に追加してみたところ。

小さな事からコツコツと、ということで少しずつ進んでいけるのが楽しい。