Problems with NSUserScriptTask

I tried to do (see this and this ) with the last NSUserScriptTask class and its subclasses, and so far I have solved some problems, but some others still have to be solved. As you can see from the docs , NSUserScriptTask does not allow canceling tasks. So, I decided to create a simple executable file that takes the path to the script as arguments and runs the script. That way, I can start the helper from my main application using NSTask and call [task terminate] when necessary. However, I need:

  • The main application for receiving output and errors from the assistant created by him.
  • Helper terminates only when NSUserScriptTask is executed

The code for the main application is simple: just run NSTask with the relevant information. Here is what I have now (for simplicity, I ignored the code for bookmarks with security protection, etc., which come out of the problem. But do not forget that this works in the sandbox):

 // 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]; 

Remember that I need the completion handler to be executed only when: (a) the task was canceled; (b) the task completed by itself, since the script was shutting down.

Now, on the side of the assistant, things are starting to get hairy, at least for me. Imagine for simplicity that the script is an AppleScript file (so I use a subclass of NSUserAppleScriptTask - in the real world, which I would have to place for three types of tasks). Here is what I got so far:

 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; } 

Now I have three questions (which I want to ask with this SO entry). Firstly , the "Completed task" is never printed (the block is never called), because the task never starts. Instead, I get this on my console:

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

I tried to run the same code from the main application, and it exits without fuss (but from the main application I lose the ability to cancel the script).

Secondly , I only want to go to the end of main ( return 0; ) after calling the completion handler. But I do not know how to do this.

Thridly , whenever an error or output from the assistant appears, I want to send this error / output back to the application, which will receive them through the errorPipe / outputPipe file. Something like fprintf(stderr/stdout, "string") does the trick, but I'm not sure if this is the right way to do this.

So, in short, any help regarding the first and second problems is appreciated. Third, I just want to make sure I have to do this.

thanks

+7
source share
2 answers

Question 1: The subtask is not completed because its parent exits immediately. (The "odd number of keys" log message is an error in NSUserScriptTask and occurs because your assistant does not have a bundle identifier, but otherwise it is harmless and has nothing to do with your problem.) It immediately exits because he does not wait to block completion, which brings us to ...

Question 2: How do you expect an asynchronous completion block? This was answered elsewhere, including Wait until all network requests are completed, including their termination blocks , but to repeat, use send groups, for example:

 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); 

The same template works for any call that has a completion block that you want to wait for. If you need another notification when the group finishes, rather than waiting for it, use dispatch_group_notify instead of dispatch_group_wait .

+4
source

As a side note, the way you checked error after highlighting NSUserAppleScriptTask is incorrect. The value of error determined if and only if the result of the function is nil (or NO, or something like failure). If the function succeeds (what you know, if it returns non-nil), then error can be anything - the function can set it to zero, it can leave it undefined, it can even fill it with a real object. (See Also What is an error (NSError **)? )

+3
source

All Articles