Вывод NSTask в реальном времени

У меня есть 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

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


person akashivskyy    schedule 03.08.2011    source источник


Ответы (2)


Вы можете использовать метод 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);
    }
}
person ughoavgfhw    schedule 03.08.2011
comment
Я пробовал это, но он регистрируется только сначала. второй и третий регистрируются при завершении задачи... - person akashivskyy; 03.08.2011
comment
Решил это, поместив дополнительные readInBackgroundAndNotify в -receiveData. Спасибо за идею! - person akashivskyy; 04.08.2011
comment
Я обновил код после того, как некоторое время боролся. Вам нужен был другой waitForDataInBackgroundAndNotify, а не readInBackgroundAndNotify. - person citelao; 01.12.2011
comment
Попробовал этот фрагмент кода с командой find, которая создает длинный результат, а для фрагмента требуется дополнительный [fh waitForDataInBackgroundAndNotify], чтобы получить полные данные. Надеюсь, это поможет таким новичкам, как я. - person Jifeng Zhang; 28.02.2013
comment
Я пытался использовать этот код в Swift (ниже) в iOS, но, что интересно, я получаю обратный вызов в режиме реального времени только тогда, когда мое приложение подключено к отладчику... Если я запускаю его автономно, я получаю только 1 вызов в конце со всем вместо приятного потока в реальном времени. Любая идея, почему? - person 7ball; 18.10.2017
comment
@7ball, скорее всего, отладчик меняет настройки буферизации на одном конце или на обоих. Вероятно, что-то, что вы можете настроить, но я не знаю, как с макушки головы. - person ughoavgfhw; 20.10.2017

Для справки, вот быстрый ответ 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() не сработает, потому что задача ожидает, пока канал опустеет, чтобы она могла записать больше, пока ваша программа ожидает окончания данных. Таким образом, ваша программа зависнет. Эта конструкция уведомления и наблюдателя позволяет асинхронно читать канал и, таким образом, предотвращает вышеупомянутый тупик.

person KernelSanders    schedule 03.10.2014