Вывод NSTask в реальном времени - PullRequest
20 голосов
/ 03 августа 2011

У меня есть PHP-скрипт с несколькими командами sleep().Я хотел бы выполнить это в моем приложении с NSTask.Мой сценарий выглядит так:

echo "first\n"; sleep(1); echo "second\n"; sleep(1); echo "third\n";

Я могу выполнить свою задачу асинхронно, используя уведомления:

- (void)awakeFromNib {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath: @"/usr/bin/php"];

    NSArray *arguments;
    arguments = [NSArray arrayWithObjects: @"-r", @"echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";", nil];
    [task setArguments: arguments];

    NSPipe *p = [NSPipe pipe];
    [task setStandardOutput:p];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskExited:) name:NSTaskDidTerminateNotification object:task];

    [task launch];

}

- (void)taskExited:(NSNotification *)notif {
    NSTask *task = [notif object];
    NSData *data = [[[task standardOutput] fileHandleForReading] readDataToEndOfFile];
    NSString *str = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
    NSLog(@"%@",str);
}

Мой вывод (конечно, через 2 секунды):

2011-08-03 20:45:19.474 MyApp[3737:903] first
second
third

Мой вопрос: как я могу получить эти три слова сразу после того, как они напечатаны?

Ответы [ 2 ]

23 голосов
/ 03 августа 2011

Вы можете использовать метод NSFileHandle waitForDataInBackgroundAndNotify для получения уведомления, когда скрипт записывает данные в свой вывод.Однако это будет работать только в том случае, если интерпретатор немедленно отправляет строки.Если он буферизует вывод, вы получите одно уведомление после завершения задачи.

- (void)awakeFromNib {
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath: @"/usr/bin/php"];

    NSArray *arguments;
    arguments = [NSArray arrayWithObjects: @"-r", @"echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";", nil];
    [task setArguments: arguments];

    NSPipe *p = [NSPipe pipe];
    [task setStandardOutput:p];
    NSFileHandle *fh = [p fileHandleForReading];
    [fh waitForDataInBackgroundAndNotify];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedData:) name:NSFileHandleDataAvailableNotification object:fh];

    [task launch];

}

- (void)receivedData:(NSNotification *)notif {
    NSFileHandle *fh = [notif object];
    NSData *data = [fh availableData];
    if (data.length > 0) { // if data is found, re-register for more data (and print)
        [fh waitForDataInBackgroundAndNotify];
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@" ,str);
    }
}
10 голосов
/ 03 октября 2014

Для справки, вот ответ ughoavgfhw в быстром темпе.

override func awakeFromNib() {
    // Setup the task
    let task = NSTask()
    task.launchPath = "/usr/bin/php"
    task.arguments = ["-r", "echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";"]

    // Pipe the standard out to an NSPipe, and set it to notify us when it gets data
    let pipe = NSPipe()
    task.standardOutput = pipe
    let fh = pipe.fileHandleForReading
    fh.waitForDataInBackgroundAndNotify()

    // Set up the observer function
    let notificationCenter = NSNotificationCenter.defaultCenter()
    notificationCenter.addObserver(self, selector: "receivedData:", name: NSFileHandleDataAvailableNotification, object: nil)

    // You can also set a function to fire after the task terminates
    task.terminationHandler = {task -> Void in
           // Handle the task ending here
    }

    task.launch()
}

func receivedData(notif : NSNotification) {
    // Unpack the FileHandle from the notification
    let fh:NSFileHandle = notif.object as NSFileHandle
    // Get the data from the FileHandle
    let data = fh.availableData
    // Only deal with the data if it actually exists
    if data.length > 1 {
    // Since we just got the notification from fh, we must tell it to notify us again when it gets more data
        fh.waitForDataInBackgroundAndNotify()
        // Convert the data into a string
        let string = NSString(data: data, encoding: NSASCIIStringEncoding)
        println(string!)
    }
}

Эта конструкция будет необходима, если ваша задача выдает много данных в трубу.Простой вызов pipe.fileHandleForReading.readDataToEndOfFile() не сработает, поскольку задача ожидает опустошения канала, поэтому она может записывать больше, пока ваша программа ожидает окончания данных.Таким образом, ваша программа будет зависать.Эта конструкция уведомления и наблюдателя позволяет асинхронно читать канал и таким образом предотвращает вышеупомянутый тупик.

...