Проблема размера выходного буфера 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 хочет, чтобы мы использовали.

Другие вопросы по тегам