我一直在尝试使用最近的NSUserScriptTask类及其子类(请参见thisthis)来做,到目前为止,我已经解决了一些问题,但是还有一些问题尚待解决。从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子类-在现实世界中,我必须适应这三种类型的任务)。这是到目前为止我得到的:
    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;
    }
    

    现在,我有三个问题(这是我想在此SO条目中提出的问题)。 首先,“完成的任务”永远不会被打印(该块永远不会被调用),因为该任务甚至从未开始执行。相反,我在控制台上得到了这个:
    MessageTracer: msgtracer_vlog_with_keys:377: odd number of keys (domain: com.apple.automation.nsuserscripttask_run, last key: com.apple.message.signature)
    

    我尝试从主应用程序运行完全相同的代码,并且完成时没有大惊小怪的事情(但是从主应用程序中,我失去了取消脚本的能力)。

    其次,,我只想在调用完成处理程序后到达main(return 0;)的末尾。但是我不知道该怎么做。

    十分有用,每当有错误或帮助程序的输出时,我都希望将该错误/输出发送回应用程序,该应用程序将通过errorPipe/outputPipe接收它们。像fprintf(stderr/stdout, "string")这样的东西可以解决问题,但是我不确定这是否是正确的方法。

    因此,简而言之,感谢您对第一个和第二个问题的任何帮助。我只想确保这是我应该做的第三个事情。

    谢谢

    最佳答案

    问题1:子任务无法运行,因为其父级立即退出。 (有关“奇数个键”的日志消息是NSUserScriptTask中的一个错误,发生的原因是您的帮助程序没有捆绑标识符,但它对您的问题无害且与您的问题无关。)由于不等待代码而立即退出。完工块着火了,这使我们...

    问题2:如何等待异步完成块?这已经在其他地方得到了回答,包括Wait until multiple networking requests have all executed - including their completion blocks,但是要回顾一下,请使用调度组,如下所示:

    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

  • 10-08 01:44