【问题标题】:NSTask output buffer size issue (Running SPApplicationsDataType command )NSTask 输出缓冲区大小问题(运行 SPApplicationsDataType 命令)
【发布时间】:2016-02-10 15:01:50
【问题描述】:

尝试从系统分析器中读取一些信息。为此,我正在使用 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

注意:是的,我可以将此数据写入文件并从该文件中读取。但我试图了解问题所在。

【问题讨论】:

    标签: swift macos buffer nstask system-profiler


    【解决方案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
    }
    

    【讨论】:

    • 这是一个很好的答案?我试图从 git 中读取大量数据,而 NSTask 只是冻结了。这使它起作用。
    【解决方案2】:

    我在新的 Mac Pro 上遇到了类似的问题,但更糟。使用 macOS 10.15.3 Catalina,我无法获取“SPAudioDataType”的 system_profiler 数据。可以调用 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 希望我们使用的产品发生根本性变化。

    【讨论】:

      猜你喜欢
      • 2011-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-04
      • 2014-10-01
      • 1970-01-01
      • 2012-02-06
      • 2018-12-12
      相关资源
      最近更新 更多