Проблема размера выходного буфера NSTask (команда SPApplicationsDataType)
Попробуйте прочитать информацию из системного профилировщика. Для этого я запускаю некоторые команды терминала с помощью NSTask. Если я запускаю какую-то команду, выход которой не слишком велик, проблем не возникает.(Например: SPInstallHistoryDataType) Но если я запускаю команду "SPApplicationsDataType" для сбора списка установленных приложений, NSTask ждет слишком много без какого-либо результата и каких-либо ошибок.
Итак, я начал понимать, что должен быть размер буфера или что-то в этом роде, и я не мог ничего найти по этому поводу. Я не знаю, может быть, я на неправильном пути.
func readData (dataType: String) -> NSArray? {
let out = NSPipe()
let task = NSTask()
task.launchPath = "/usr/sbin/system_profiler"
task.arguments = ["-xml",dataType]
task.standardOutput = out
task.launch()
task.waitUntilExit()
if task.terminationStatus != 0 {
NSLog("system_profiler returned error status")
return nil
}
let data = out.fileHandleForReading.readDataToEndOfFile()
let plist : AnyObject?
do {
plist = try NSPropertyListSerialization.propertyListWithData(data,
options: [.Immutable],
format: nil)
} catch let error as NSError {
NSLog("%@", "Failed to parse system_profiler results. \(error.localizedDescription)")
return nil
}
return plist as? NSArray
}
let r = readData("SPInstallHistoryDataType")// There is no problem
let r2 = readData("SPApplicationsDataType") // Crash
Примечание: Да, я мог бы записать эти данные в файл и прочитать из этого файла. Но я пытаюсь понять, в чем проблема.
1 ответ
Это определенно проблема с буфером. Когда вы читаете порцию за раз, это работает.
func getApplications() -> String?
{
var retval=""
let theTask = NSTask()
let taskOutput = NSPipe()
theTask.launchPath = "/usr/sbin/system_profiler"
theTask.standardOutput = taskOutput
theTask.standardError = taskOutput
theTask.arguments = ["-xml", "SPApplicationsDataType"]
theTask.launch()
while (true) {
let data = taskOutput.fileHandleForReading.readDataOfLength(1024)
if (data.length <= 0) { break }
let str = String(data: data, encoding: NSUTF8StringEncoding)!
retval += str
//print (str)
}
theTask.waitUntilExit()
return retval
}
У меня похожая проблема на новом Mac Pro, но еще хуже. В macOS 10.15.3 Catalina я не могу получить данные system_profiler для "SPAudioDataType". Могут быть вызваны другие процессы, такие как curl и т. Д., Но проблема с system_profiler.
Самая забавная вещь с моей проблемой заключалась в том, что она возникла примерно через 10 минут после перезапуска. В первые 10 минут все работало, с обработчиками или без них и даже с кодом getApplications из ответа выше.
И да, конечно, я запускаю его в основном потоке, но не имеет значения, работает он в основном потоке или нет.
Я много экспериментировал, чтобы посмотреть, что является источником такого поведения. Выяснил, что мои программы зависают при чтении данных командой
let data = taskOutput.fileHandleForReading.readDataOfLength(1024)
в случае наличия данных об ошибках и наоборот, программа зависает при чтении сообщений об ошибках с помощью команды
let data = taskError.fileHandleForReading.readDataOfLength(1024)
в случае, если есть нормальные данные (но нет данных об ошибках).
Программа даже зависает, если я пытался получить тот объем данных, который сейчас доступен:
let c = taskError.fileHandleForReading.availableData.count
Независимо от того, что я тестирую в первую очередь, программа зависает, если нет данных.
Поэтому я полностью переписал свою функцию на использование асинхронных обработчиков:
@discardableResult func launchprogram (_ launchpath: String, _ arguments: [String]) -> (result: String, error: Int)
{
var out: String = "" // Output
var err: String = "" // Error Messages
var fin: Bool = false // If the process exits normally
let pro: Process = Process()
pro.arguments = arguments
pro.launchPath = launchpath
pro.standardOutput = Pipe()
pro.standardError = Pipe()
let proOut: Pipe = pro.standardOutput as! Pipe
let proIn: Pipe = pro.standardError as! Pipe
proOut.fileHandleForReading.readabilityHandler =
{
pipe in
if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8)
{
if line.count > 0 // Neuen Ausgabe-Text hinzufügen
{
out += line
}
}
}
proIn.fileHandleForReading.readabilityHandler =
{
pipe in
if let line = String(data: pipe.availableData, encoding: String.Encoding.utf8)
{
if line.count > 0 // Neuen Fehler-Text hinzufügen
{
err += line
}
}
}
pro.terminationHandler =
{
(process) in
fin = not(process.isRunning)
}
pro.launch()
pro.waitUntilExit()
if err == ""
{
if fin
{
return (out, 0)
}
else
{
return (out, -1)
}
}
else if out == ""
{
let message: String = "Error while executing:" + char(13) + char(13)
return (message + err, -2)
}
else
{
let message: String = char(13) + char(13) + "Error while executing:" + char(13) + char(13)
return (out + message + err, -3)
}
}
Принципиальное различие между этой функцией и функцией "getApplications" из предыдущего сообщения состоит в том, что я использую "обработчик" для управления потоками вывода и сообщений об ошибках. Это всегда работает. Целевое значение развертывания может быть 10,9 или выше. На 10.8 и ранее не тестировал. Итак, моя проблема заключалась в том, что в Catalina при некоторых обстоятельствах больше невозможно получить информацию в "нормальном" синхронном порядке, а только асинхронно с использованием обработчиков. Если я прерву выполнение, я всегда буду в чем-то вроде "libsystem_kernel.dylibread" withe the calling function "Foundation
_NSReadFromFileDescriptorWithProgress". Я был бы рад узнать, является ли это проблемой Catalina (с новым Mac Pro) или фундаментальным изменением в том, что Apple хочет, чтобы мы использовали.