2017-01-18 2 views
0

Я загружаю XML-файл из API с помощью URLSessionDataTask.
XML-выглядит следующим образом:Большое использование ОЗУ при анализе XML с использованием Libxml2

<?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 может иметь тысячи книг.
Я разбираю XML с помощью анализатора SAX из Libxml2. В процессе анализа я создаю объект 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) делегата добавляет объект книги в массив и обновляет UITableView. Все идет нормально.

Проблема теперь в том, что она занимает слишком много ОЗУ устройства, и поэтому мое устройство становится медленным. При ~ 500 книгах в XML требуется 500 МБ ОЗУ. Я не знаю почему. Когда я подстановки ОЗУ в инструментах, я вижу всю выделенную память в категории _HeapBufferStorage<_StringBufferIVars, UInt16>

Instruments

С множеством записей больше, чем 100 KB

HeapBufferStorage entries В истории событий метод buildBook() перечисленных

Event History

Когда я использую XmlParser из фонда с с onstructor XMLParser(contentsOf: URL), который сначала загружает весь XML, а затем анализирует его, у меня нормальное использование ОЗУ. Неважно, сколько книг. Но я хочу показать книги как можно скорее в UITableView. Я просто хочу что-то вроде XMLPullParser от Android для iOS.

+0

SAX парсер должен использовать небольшую память для анализа данных XML, вы хотите попробовать другой парсер? – aircraft

+0

Я думал о [интерфейсе XMLTextReader] (http: // xmlsoft.org/xmlreader.html) из LibXML2, потому что это «преемник». Но знаете ли вы, могу ли я использовать его во время загрузки XML-файла? – ElegyD

ответ

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) 
Смежные вопросы