UNIXコマンドを非同期で実行する
SwiftでMacアプリを作るときに。
参考:UNIXコマンドを実行する(非同期)
参考:【OSX】【swift】コマンドの実行
非同期で、対話式に出力を取りたい時、少し罠があるのと、面倒が多いので、罠を乗り越えたクラスを作りました。
リポジトリ:https://github.com/ha1fha1f/CommandTask
重要なのはCommandTask.swiftだけです。
使い方は、
let task = CommandTask(cmd: "/usr/bin/curl", arguments: ["https://qiita.com"])
.addObserver { string in
print("observe", string)}
.addCompletionHandler {
print("complete")}
.launch()
とするだけです。(参照:ViewController.swift)
自分のPCで実行する場合、コマンドのパスは、ターミナルで which コマンド名
とすれば調べられます。
対話式に入力したいときは、
task.writeln("今日はいい天気ですね")
を実行すればよいです。よく考えれば当たり前なのですが、writeのあとに "\n"
を入れないと入力が完了しないのに、入れ忘れて結構時間無駄にしましたので、自動で入力するメソッドを用意しました。
その他罠としては、terminationHandlerが呼ばれたあとも、NSFileHandleDataAvailableNotificationのオブザーバーが呼ばれる点です。これには完了処理を呼ばれたあとで判断することによって対処しました。
ネットのコードを見ていると、 NSTaskDidTerminateNotification
を用いているものもありますが、標準で用意されている terminationHandler
を用いたほうが良いと思います。タイミングは同じです。
実行ファイルをプロジェクトの中に入れる
実行ファイルのバイナリは、もちろんアプリの中に入れることもできます。archiveしたあとも、.app内部に入ります。
その場合は、プロジェクト内の適当なところにファイルを配置しておいて、
NSBundle.mainBundle().pathForResource("ファイル名", ofType: "")
とすれば取得できます。これをCommandTaskのイニシャライザの引数にセットすればOKです。
不安点
メモリ管理、どうなってるのかわからなくて不安なので、詳しい方はコメントいただけると嬉しいです。
現状のコードだと、commandTaskのインスタンスを、viewcontrollerのプロパティなどとして保存しておかない)と、参照が0になって、commandTaskのプロパティとして保存しているobserverとかが消失して、呼ばれなくなるので、その辺修正の必要を感じています