使用Libxml2解析XML时使用大量RAM

时间:2017-01-18 13:57:52

标签: ios xml swift libxml2

我使用URLSessionDataTask从API下载XML文件 XML看起来像这样:

<?xml version="1.0" encoding="UTF-8" ?>
<ResultList id="12345678-0" platforms="A;B;C;D;E">
    <Book id="1111111111" author="Author A" title="Title A" price="9.95" ... />
    <Book id="1111111112" author="Author B" title="Title B" price="2.00" ... />
    <Book id="1111111113" author="Author C" title="Title C" price="5.00" ... />
    <ResultInfo bookcount="3" />
</ResultList>

有时XML可能有数千本书 我使用Libxml2中的SAX解析器解析XML。在解析时,我创建了一个对象Book,并像这样设置XML中的值:

private func startElementSAX(_ ctx: UnsafeMutableRawPointer?, name: UnsafePointer<xmlChar>?, prefix: UnsafePointer<xmlChar>?, URI: UnsafePointer<xmlChar>?, nb_namespaces: CInt, namespaces: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?) {

    let elementName = String(cString: name!)

    switch elementName {
    case "Book":
        let book = buildBook(nb_attributes: nb_attributes, attributes: attributes)
        parser.delegate?.onBook(book: book)
    default:
        break
    }
}

func buildBook(nb_attributes: CInt, attributes: UnsafeMutablePointer<UnsafePointer<xmlChar>?>?) -> Book {
    let fields = 5 /* (localname/prefix/URI/value/end) */
    let book = Book()
    for i in 0..<Int(nb_attributes) {
        if let localname = attributes?[i * fields + 0],
            //let prefix = attributes?[i * fields + 1],
            //let URI = attributes?[i * fields + 2],
            let value_start = attributes?[i * fields + 3]//,
            /*let value_end = attributes?[i * fields + 4]*/ {

                let localnameString = String(cString: localname)
                let string_start = String(cString: value_start)
                //let string_end = String(cString: value_end)

                if let end = string_start.characters.index(of: "\"") {
                    let value = string_start.substring(to: end)
                    book.setValue(value, forKey: localnameString)
                } else {
                    book.setValue(string_start, forKey: localnameString)
                }
        }
    }
    return book
}

在UITableViewController中,onBook(book: Book)委托方法将book对象附加到数组并更新UITableView。到目前为止一切都很好。

现在的问题是,它需要设备的RAM太多,因此我的设备会变慢。在XML中约有500本书需要> 500 MB的RAM。我不知道为什么。当我在Instruments中查找RAM时,我会在类别_HeapBufferStorage<_StringBufferIVars, UInt16>

中看到所有已分配的内存

Instruments

多个条目大于100 KB

HeapBufferStorage entries 在事件历史记录中列出了buildBook()方法

Event History

当我使用Foundation的XMLParser和构造函数XMLParser(contentsOf: URL)时,它首先下载整个XML然后解析它,我有正常的RAM使用率。无论有多少本书。但是我想在UITableView中尽快显示这些书。我只想要像Android的XMLPullParser for iOS。

1 个答案:

答案 0 :(得分:0)

我正在使用libxml2(由于this问题),并且代码如下:

xmlParseChunk(ctxt, data, Int32(read), 0)

更改对此的调用会大大减少占用的内存量:

autoreleasepool {
    xmlParseChunk(ctxt, data, Int32(read), 0)
}

如果您正在使用上述推送解析器调用,则可能会解决您的问题。如果没有,那么在autoreleasepool调用中包装你的委托调用可能会有所帮助。

原因是因为正在创建许多中间对象并将其添加到自动释放池中而不会被释放。有关详细信息,请参阅this帖子。

另一种方法是通过以其他方式更改代码来减少添加到自动释放池的对象数。我发现,例如,我通过在可以避免它的地方修剪空白来创建额外的字符串。

此外,这与您的问题无关,但属性的开头和结尾告诉您字符串的长度,您应该使用它。

例如:

let valStart = UnsafeMutableRawPointer(mutating: attributes!
    .advanced(by: 3 + Int(i * 5)).pointee)
let valEnd = UnsafeMutableRawPointer(mutating: attributes!
    .advanced(by: 4 + Int(i * 5)).pointee)
let valData = Data(bytesNoCopy: valStart!, count: valEnd! - valStart!, 
    deallocator: .none)
let attrValue = String(data: valData, encoding: String.Encoding.utf8)