Objective-C, NSTask Ограничение буфера - PullRequest
1 голос
/ 21 марта 2019

Я использую NSTask для запуска внешней утилиты, которая возвращает длинную строку данных. Проблема в том, что когда возвращаемая строка превышает большой объем данных (около 32759 символов), она становится null или усекает возвращаемую строку. Как вернуть полный вывод?

NSTask *myTask = [[NSTask alloc] init];

[myTask setLaunchPath:myExternalCommand];
[myTask setArguments:[NSArray arrayWithObjects: arg1, arg2, nil]];

NSPipe *pipe = [NSPipe pipe];
[myTask setStandardOutput:pipe];

NSFileHandle *taskHandle;
taskHandle = [pipe fileHandleForReading];

[myTask launch];
[myTask waitUntilExit];

NSData *taskData;
taskData = [taskHandle readDataToEndOfFile];

NSString *outputString = [[NSString alloc] initWithData:taskData
                         encoding:NSUTF8StringEncoding];

NSLog(@"Output: \n%@", outputString);
// (null or truncated) when stdout exceeds x amount of stdout

Для проверки работоспособности используйте cat или аналогичный для большого файла для myExternalCommand. Кажется, проблема возникает сразу после длины символа 32759 ...

решение? Я не уверен, но может потребоваться как-то прочитать возвращаемые значения stdout порциями, а затем добавить данные outputString, если это возможно.

обновление: я пытался переместить waitUntilExit после readDataToEndOfFile за предложение, но это не повлияло на результат.

* пожалуйста, обратите внимание, я ищу Obj-C решение, спасибо.

1 Ответ

4 голосов
/ 26 марта 2019

Найдено на CocoaDev :

«Данные, которые проходят через канал, буферизуются; размер буфера определяется базовой операционной системой ».

от: http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSPipe_Class/index.html

Предел буфера NSPipe, по-видимому, составляет 4096 байт (ср. /Usr/include/limits.h: «… #define _POSIX_ARG_MAX 4096…»)

Вы можете читать вывод из вашего NSTask асинхронно, используя readabilityHandler . Внутри обработчика, используйте availableData чтобы читать вывод по частям.

Используйте terminationHandler , чтобы получать уведомления после завершения задания, а затем установите readabilityHandler на ноль, чтобы остановить его чтение.

Это все асинхронно, поэтому вам нужно заблокировать и дождаться завершения задачи.

Вот полный пример, который мне подходит. Я использовал printf вместо NSLog, так как кажется, что NSLog обрезает вывод на консоли (не уверен, что это ошибка или функция ). Проверка ошибок опущена и добавляет некоторую сложность, вам, вероятно, захочется также прочитать standardError.

dispatch_semaphore_t waitHandle;
NSTask *myTask;
NSMutableData* taskOutput;

waitHandle = dispatch_semaphore_create(0);

myTask = [[NSTask alloc] init];
[myTask setLaunchPath:@"/bin/cat"];
[myTask setArguments:[NSArray arrayWithObjects: @"/path/to/a/big/file", nil]];
[myTask setStandardOutput:[NSPipe pipe]];

taskOutput = [[NSMutableData alloc] init];

[[myTask.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {
    NSData *data = [file availableData];
    [taskOutput appendData:data];
}];

[myTask setTerminationHandler:^(NSTask *task) {
    [task.standardOutput fileHandleForReading].readabilityHandler = nil;

    NSString *outputString = [[NSString alloc] initWithData:taskOutput encoding:NSUTF8StringEncoding];
    printf("Output: \n%s\n", [outputString UTF8String]);

    dispatch_semaphore_signal(waitHandle);
}];

[myTask launch];

dispatch_semaphore_wait(waitHandle, DISPATCH_TIME_FOREVER);
...