テキスト全文検索ツール

macOS Swift

キーワードによりファイルを全文検索する

macOS Mojava 10.15.7 / Xcode 11.3.1 / Swift 5.0
指定されたキーワードにより、特定のフォルダにあるテキストファイルをサブフォルダも含め全て検索し、指定した検索語を含んだファイルの一覧をテーブルビューに表示する。
[image7]

検索方法

「検索語」フィールドに検索語を入力し、検索ボタンをクリックする。
検索語の前後の空白文字はトリミングする。複数の検索語を入力するときは、各検索語を空白文字で区切る。
検索語にヒットしたファイルをテーブルビューに表示する。表示項目は、フォルダ名、ファイル名、ファイルの中に含まれる検索語の数、ファイルのサイズ(バイト数)である。それぞれの項目をキーにしてレコードのソートを行うことができる。列見出しをクリックすると昇順/降順のソートを繰り返す。

設定項目

フォルダの選択

対象フォルダは、[選択] ボタンのクリックにより開くオープンパネルから指定するか、[対象フォルダ] フィールドに直接パスを入力する。
テキストファイルか否かは、ファイルの拡張子により判定する。中身がテキストでも、拡張子がない、あるいは正しくないときは検索対象にならない。

ファイルタイプの絞り込み

「ファイルの拡張子」フィールドにファイルの拡張子を入すれば、検索対象のファイルを絞り込むことができる。空白で区切って複数指定することができる。一方、「除外」をオンにすると指定した拡張子のファイルが検索対象から外れる指定となる。
[image1]

AND / OR 検索

AND / OR 検索を選択する。AND 検索は、ファイルに検索語が全て含まれていた場合、「真」の判定になる。OR 検索は、ファイルにに検索語がひとつでも含まれていた場合、真」の判定になる。
[image2]
As is 検索
検索語をダブルコーテーションで囲んだ場合、その文字列は空白も含めてひとつの検索語とし、そのまま検索する。
[image3]

正規表現による検索

正規表現を使って検索をすることができる。
[image8]
検索語に一般的な正規表現を指定することができる。
[image4]
ただし、空白文字は複数検索語の区切り文字として扱うので、空白を指定する場合は、"\s" などのメタ文字を使用すること。

エディタの選択

検索結果として一覧表に表示されたファイルは、テキストエディターで開くことができる。テーブルビューのレコードをダブルクリックすると、事前に設定したエディターが起動する。エディターの種類は次の中から選ぶことができる。
[image6]

クラス一覧

AppDelegate

extension AppDelegate

処理が多いので2分割した。UIコントロール、メニュー管理、設定項目の管理等を行う。

UASearchMgr

検索処理の本体。主な処理は、
(1) 検索対象となるファイル一覧の作成
(2) テキストファイルの読み込みと全文検索
からなる。なお、(2)はマルチスレッドによる並列処理とする。

ObjcLib.h

ObjcLib.m

FindingWords-Bridging-Header.h

検索対象となるファイル一覧の作成
ディレクトリを再起的に読み込み、検索対象となるファイルの一覧を作成する。Swiftのライブラリでなく、POSIXの APIを使った方がパフォーマンスが良いので、メソッドは Objective-Cで作成し、Swiftからブリッジを経由して呼び出す。

extension String

キーワードによるテキスト検索処理を Stringクラスの extentionとして定義する。Stringクラスの rangeメソッドによる検索、および正規表現(NSRegularExpressionクラス)による検索の二つを提供する。

UATableViewMgr

検索結果の一覧表(テーブルビュー)を制御する。

SheetController

検索処理の進捗を表示するプログレスバーを制御する。

extension Int

extension NSButton

MainMenu.xib

メイン画面

Progress.xib

プログレスバー表示ダイアログ

クラス関連図

[findingWords2-2]

並列処理について

処理時間を短縮するために、検索処理を複数のタスクに分割し、マルチスレッド機能を利用して並列に実行する。タスク数は、メニューの [設定] → [並列処理数] から 2〜10の範囲で選択できる。タスク数は 10ぐらいが適当と考える。(色々試してみたがあまり大きな差は出なかった)
検索ファイル数が大量の場合(100,000ファイル超)は長時間の処理が予想されるので、処理の前に警告ダイアログを表示し、実行の可否を問い合わせることにしている。 一方、検索ファイル数が少ない場合(1,000以下)は、並列化しても意味がないので、1スレッドで処理している。
処理の進捗率(検索対象ファイル数に対する検索済みのファイル数の割合)をプログレスバーに表示する。中止ボタンをクリックすることにより、実行中の処理を終了させることができる。
処理の並列化(マルチスレッド処理)は Grand Central Dispatch (GCD) を利用する。キューはコンカレントとし、プログレスバーへのデータ出力、コントロールからのイベントの受け付けを可能とするため、非同期(async)処理とする。
非同期処理である以上、複数スレッドの処理の終了を待って次の処理に進むには、同期の制御が必要になる。本アプリケーションではカウンタ変数を用意し、検索処理がひとつ終わるごとにカウンタをインクリメント、全てが終わった時点で次の処理に進むようにした。こうすれば、メインスレッドのイベントループは待ち受け可能なので、UIからのイベントの取得や、UI要素に対する更新をスレッド処理中でも行うことができる。
注意事項
コンカレントに起動した複数のサブスレッドから、メインスレッドの変数にアクセスするときは注意が必要。配列オブジェクトへの要素の追加は、スレッドセーフでないので、代入時の競合でシステムクラッシュする可能性がある。
また、クラッシュしなくとも、プロパティの値が不正に更新される可能性がある。例えば、プロパティの値を読み、それを元に計算し、書き戻すといった一連の処理があるとする。並列処理で、スレッドAがプロパティを読み込んで計算している間に、スレッドBもプロパティを読み込み計算を行う。そして、Aが結果を書き込んだあと、Bも書き込む。結果は不正な値となる。正しくは、Bは、Aが計算結果を書き込むまで、プロパティの読み込みを待たなければいけない。(データベース障害事例あるある)
以上の事象を避けるためには、一連の処理をブロック化し、シリアルキューを通して順次に実行すればよい。シリアルキューを通して起動した処理は、それが終わるまで、シリアルキューに投入された別の処理を待たせることになり、これにより各処理の前後の一貫性は保証される。
本アプリケーションでは、メインスレッドで実行される次のシリアルキューを使用している。

状態の保存

表示中のウィンドウの外観(ウィンドウの大きさ、テーブルビューの列の並び順と幅)は、アプリケーション終了時に、設定値をオブジェク化して、「ユーザーデフォルト」に保存し、次回アプリケーション起動時にに引き継ぐ。
メニューの設定情報は、アプリケーション終了時に「plist」に保存して、次回アプリケーション起動時にに引き継ぐ。保存場所は ~Documents/DirectoryTraverse.plist である。