7

私は最近の NSUserScriptTask クラスとそのサブクラスを使って何とかしようとしてきました (これこれを見てください)。docsからわかるように、 NSUserScriptTask はタスクのキャンセルを許可しません。そこで、スクリプトへのパスを引数として取り、スクリプトを実行する単純な実行可能ファイルを作成することにしました。そうすれば、NSTask を使用してメイン アプリからヘルパーを起動し、[task terminate]必要に応じて呼び出すことができます。ただし、次のものが必要です。

  • 起動したヘルパーから出力とエラーを受け取るメイン アプリ
  • NSUserScriptTask が完了したときにのみ終了するヘルパー

メイン アプリのコードは単純です。適切な情報を指定して NSTask を起動するだけです。これが私が今持っているものです(簡単にするために、セキュリティスコープのブックマークなどのコードは無視しましたが、これは問題外です。ただし、これがサンドボックスで実行されていることを忘れないでください):

// Create task
task = [NSTask new];
[task setLaunchPath: [[NSBundle mainBundle] pathForResource: @"ScriptHelper" ofType: @""]];
[task setArguments: [NSArray arrayWithObjects: scriptPath, nil]];

// Create error pipe
NSPipe* errorPipe = [NSPipe new];
[task setStandardError: errorPipe];

// Create output pipe
NSPipe* outputPipe = [NSPipe new];
[task setStandardOutput: outputPipe];

// Set termination handler
[task setTerminationHandler: ^(NSTask* task){        
    // Save output
    NSFileHandle* outFile = [outputPipe fileHandleForReading];
    NSString* output = [[NSString alloc] initWithData: [outFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];

    if ([output length]) {
        [output writeToFile: outputPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
    }

    // Log errors
    NSFileHandle* errFile = [errorPipe fileHandleForReading];
    NSString* error = [[NSString alloc] initWithData: [errFile readDataToEndOfFile] encoding: NSUTF8StringEncoding];

    if ([error length]) {
        [error writeToFile: errorPath atomically: NO encoding: NSUTF8StringEncoding error: nil];
    }

    // Do some other stuff after the script finished running <-- IMPORTANT!
}];

// Start task
[task launch];

(a) タスクがキャンセルされた (b) スクリプトの実行が終了したため、タスクが勝手に終了した場合にのみ、終了ハンドラーを実行する必要があることを思い出してください。

さて、ヘルパー側では、少なくとも私にとっては厄介なことになり始めています。簡単にするために、スクリプトが AppleScript ファイルであると想像してみましょう (そのため、NSUserAppleScriptTask サブクラスを使用します。現実の世界では、3 種類のタスクに対応する必要があります)。これが私がこれまでに得たものです:

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        NSString* filePath = [NSString stringWithUTF8String: argv[1]];
        __block BOOL done = NO;

        NSError* error;
        NSUserAppleScriptTask* task = [[NSUserAppleScriptTask alloc] initWithURL: [NSURL fileURLWithPath: filePath] error: &error];

        NSLog(@"Task: %@", task); // Prints: "Task: <NSUserAppleScriptTask: 0x1043001f0>" Everything OK

        if (error) {
            NSLog(@"Error creating task: %@", error); // This is not printed
            return 0;
        }

        NSLog(@"Starting task");

        [task executeWithAppleEvent: nil completionHandler: ^(NSAppleEventDescriptor *result, NSError *error) {
            NSLog(@"Finished task");

            if (error) {
                NSLog(@"Error running task: %@", error);
            }

            done = YES;
        }];

        // Wait until (done == YES). How??
    }

    return 0;
}

ここで、3 つの質問があります (この SO エントリで聞きたい質問です)。まず、「終了したタスク」は印刷されません (ブロックは呼び出されません)。これは、タスクが実行を開始することさえないためです。代わりに、コンソールでこれを取得します。

MessageTracer: msgtracer_vlog_with_keys:377: odd number of keys (domain: com.apple.automation.nsuserscripttask_run, last key: com.apple.message.signature)

メインアプリからまったく同じコードを実行してみましたが、大騒ぎせずに完了しました(ただし、メインアプリからはスクリプトをキャンセルできなくなりました)。

第二return 0;に、完了ハンドラが呼び出された後にのみ main() の最後に到達したい。しかし、私はそれを行う方法がわかりません。

三番目に、ヘルパーからのエラーまたは出力があるときはいつでも、そのエラー/出力をアプリに送り返し、アプリは errorPipe/outputPipe を介してそれらを受け取ります。のような何かfprintf(stderr/stdout, "string")がトリックを行いますが、それが正しい方法であるかどうかはわかりません。

したがって、要するに、最初と 2 番目の問題に関するヘルプは大歓迎です。3 つ目は、それが本来のやり方であることを確認したいだけです。

ありがとう

4

2 に答える 2

4

質問 1: 親がすぐに終了するため、サブタスクは実行されません。(「奇数のキー」に関するログ メッセージは のバグでNSUserScriptTaskあり、ヘルパーがバンドル ID を持っていないために発生しますが、それ以外は無害であり、問​​題とは無関係です。) 完了を待っていないため、すぐに終了します。ブロックして発砲すると、次のことが起こります...

質問 2: 非同期完了ブロックをどのように待ちますか? これは、複数のネットワーク要求がすべて実行されるまで待機するなど、他の場所で回答されています - 完了ブロックを含みますが、要約すると、次のようなディスパッチ グループを使用します。

dispatch_group_t g = dispatch_group_create();
dispatch_group_enter(g);
[task executeWithAppleEvent:nil completionHandler:^(NSAppleEventDescriptor *result, NSError *e) {
    ...
    dispatch_group_leave(g);
}];
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
dispatch_release(g);

これと同じパターンは、待機したい完了ブロックを持つすべての呼び出しで機能します。グループの終了時に待機するのではなく別の通知が必要な場合は、dispatch_group_notify代わりに を使用しdispatch_group_waitます。

于 2013-03-30T23:11:58.963 に答える
3

補足として、errorを割り当てた後にテストする方法NSUserAppleScriptTaskは正しくありません。の値は、関数の結果が nil (または NO、または失敗を示すもの) の場合にのみerror定義されます。関数が成功した場合 (非 nil を返すかどうかはわかります)、何でもかまいません。関数は関数を nil に設定したり、未定義のままにしたり、実際のオブジェクトで埋めることさえできます。( (NSError**) エラーのポイントは何ですか?も参照してください) 。error

于 2013-03-31T17:57:05.800 に答える