Проблема с размером буфера вывода 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

Примечание. Да, я мог бы записать эти данные в файл и прочитать из этого файла. Но я пытаюсь понять, в чем проблема.


person SerhatTopkaya    schedule 10.02.2016    source источник


Ответы (2)


Это определенно проблема с буфером. Когда вы читаете фрагмент за раз, это работает.

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
}
person Alex M    schedule 14.03.2016
comment
Это отличный ответ ???? Я пытался прочитать много данных из git, и NSTask просто завис. Это заставляет его работать. - person Sentry.co; 18.09.2017

У меня похожая проблема на новом 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 хочет, чтобы мы использовали.

person j.s.com    schedule 12.03.2020