【问题标题】:NSUserScriptTask difficultiesNSUserScriptTask 困难
【发布时间】:2013-03-14 23:42:19
【问题描述】:

我一直在尝试使用最近的 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") 之类的东西可以解决问题,但我不确定这是否是正确的做法。

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

谢谢

【问题讨论】:

  • 你看过XPC吗?
  • @Dov 我将 XPC 用于其他目的,但老实说我不明白如何使用它。你认为它会在这里做吗?
  • 我不能肯定地说,因为我没有尝试过,但它似乎更适合你正在做的事情。可能值得一看。
  • @Dov 为什么你认为它会更合适?我现在很好奇,我对 XPC 服务的了解几乎为零
  • 如果你可以针对 10.8,有一些不错的 XPC Cocoa 包装器。否则,您将自己编写一些简单的包装器。我认为 XPC 非常适合您的用例。另一种选择是使用分布式通知来发出任务完成的信号,并让主应用程序注册这些通知。 1Password 的助手应用根据情况使用 XPC 和分布式通知在助手和主应用之间进行通信。

标签: objective-c cocoa nstask appstore-sandbox


【解决方案1】:

问题 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

【讨论】:

  • 谢谢,这是一个很酷的方法。但它仍然没有解决我的问题,因为现在,虽然我在控制台上看到 @"Finished task" 并且没有 @"Error: ..." 行,但脚本仍然没有运行。例如,我正在运行的测试applescript 是say "Hello",我什么也没听到……而且对于applescript 来说它完成得太快了。 (我仍然看到odd number of keys 消息,顺便说一句)
  • 让我直截了当地说:您看到了“已完成任务”消息,因此我们知道回调正在触发,并且回调的 error 参数为 nil,因此它认为它成功了,但您听到了没有?如果直接从命令行运行帮助程序会发生什么?或者,如果您将脚本更改为仅计算的内容(例如 2+2)并记录结果?
  • 回复。 “奇数个钥匙”:正如我之前所说,这基本上是无害的——它是一些诊断代码失败。如果它真的困扰你,你可以在你的助手中添加一个带有 CFBundleIdentifier 的嵌入式 plist;见 stackoverflow.com/questions/8234946/…>.
  • 它一点也不困扰我。但经过进一步测试,我发现在运行脚本时有时会出错,但有时不会。总是同样的错误:Error running script: Error Domain=NSCocoaErrorDomain Code=257 "The file “Hello.scpt” couldn’t be opened because you don’t have permission to view it." UserInfo=0x102504800 {NSURL=file://localhost/Users/Alex/Library/Application20%Scripts/appID/Hello.scpt, NSLocalizedFailureReason=Script file is not in the application scripts folder.}
  • 从命令行我得到这个错误(还是一样的say "Hello"applescript):Error running script: Error Domain=NSPOSIXErrorDomain Code=2 "The operation couldn’t be completed. /usr/bin/osascript: couldn't set default target: no eligible process with specified descriptor " UserInfo=0x7ffefbe04f60 {NSURL=file://localhost/Users/Alex/Library/Application%20Scripts/net.ACT-Productions.Clockwise/Hello.scpt, NSLocalizedFailureReason=/usr/bin/osascript: couldn't set default target: no eligible process with specified descriptor }
【解决方案2】:

附带说明一下,您在分配 NSUserAppleScriptTask 后测试 error 的方式不正确。 error 的值被定义当且仅当函数结果为 nil(或 NO,或任何指示失败)。如果函数成功(你知道它是否返回非 nil),那么error 可能是任何东西——函数可能将它设置为 nil,它可能不定义它,它甚至可能用一个真实的对象填充它。 (另见What's the Point of (NSError**)error?

【讨论】:

  • 我明白了,你是对的。我应该用if (task)替换if (error)
猜你喜欢
  • 1970-01-01
  • 2019-08-07
  • 2016-12-24
  • 2019-07-16
  • 2021-03-09
  • 2017-04-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多