4

I have a class that iterates through all the subfolders of a given folder on OSX and sends a message to its delegate for each folder it finds.

For each folder found I want to run an NSTask. So far so good. It's when I specify a terminationHandler to the NSTask that I run into problems.

This is the code:

-(void)crawler:(FTCFileSystemCrawler *)aCrawler
 didFindFolder:(NSURL *)aURL
      withName:(NSString *)aFileName
          stop:(BOOL *)stop{

    NSTask *task = [NSTask new];
    task.launchPath = @"/usr/bin/say";
    task.arguments = @[aFileName];
    task.terminationHandler = ^(NSTask *aTask){
        NSLog(@"Terminating!");
        [self.tasks removeObject:aTask];

    };
    [self.tasks addObject:task];

    [task launch];
    //[task waitUntilExit];


}

This works fine, it after sending launch to the NSTask, I send waitUntilExit. If I don't, the termination handler never runs.

What am I doing wrong?

PS Please keep in mind that this is just a simple example. I'm actually using this to identify git repos and run a command it it.

cfischer
  • 24,452
  • 37
  • 131
  • 214

3 Answers3

2

Did you try something like this inspite of using task.terminationHandler = ^(NSTask *aTask) might be the problem is using inside this block which was not handling properly.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(taskDidTerminate:)
                                             name:NSTaskDidTerminateNotification
                                           object:nil];
Hussain Shabbir
  • 14,801
  • 5
  • 40
  • 56
  • 1
    This was going to be my next suggestion as well. The NSTaskDidTerminateNotification doesn't pass a userInfo dict though which may limit it a bit here. – macshome Oct 08 '13 at 13:59
1

NSTask in general can be pretty tricky to work with, even more so with a tool that doesn't output any text in its default configuration. As you found, waitUntilExit will solve your problem, but it raises another problem in that it is going to block until the task is done. A common way to fix this is using a NSPipe as seen in this answer [here].1 There is a much better way to do this though in Cocoa!

Rather than deal with a NSTask at all it's much simpler just to use the Cocoa API for the speech engine, NSSpeechSynthesizer. Here is a quick example of your same code converted:

-(void)crawler:(FTCFileSystemCrawler *)aCrawler
 didFindFolder:(NSURL *)aURL
      withName:(NSString *)aFileName
          stop:(BOOL *)stop{

NSSpeechSynthesizer *speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth startSpeakingString:aFileName];

}

Much simpler, you can use the speechSynthesizer:didFinishSpeaking: delegate method if you still need a callback when it's done talking.

Community
  • 1
  • 1
macshome
  • 939
  • 6
  • 11
  • Yep. Never fork a process for something that can be done easily using native APIs. – Rob Keniger Oct 08 '13 at 10:33
  • 1
    What I want to know if WHY waitUntilExit solves the problem. Actually I'm using it to identify git repos and run a series of commands in it. That's just a simple example – cfischer Oct 08 '13 at 11:02
1

It may be worth storing task into a property so as to retain it and prevent it possibly being deallocated when it reaches the end of the scope of the method. For example the following in your header or private interface:

@property (nonatomic, strong) NSTask *activeTask;

And then set that at the end of your method:

self.activeTask = task;
Joshua
  • 15,200
  • 21
  • 100
  • 172