Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse an XML file in chunks without waiting for complete download in Swift

In iOS, I would like to parse an XML stream from the server while downloading. It should not have to wait until the server is done building the XML and the download is complete. The server builds XML in "chunks" and sends it directly to the client. In my app I have a UITableView which should instantly show the elements of the XML as soon as I received it from the server.

I've tried it with the XMLParser(contentsOf: URL) constructor, but this first downloads the whole XML and then parses it. There is another constructor XMLParser(stream: InputStream), but I don't know how to get an InputStream from a URLConnection. The only thing I found was this question which is almost 5 years old, and I couldn't understand it how to do this in Swift 3, if it even works.

The other thing I tried was via Libxml2, but there I have problems with RAM usage or other things (see my old question).

How can I parse an XML stream in chunks without having to wait for a complete download in Swift?

In Android, I would use an XMLPullParser and have:

URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
InputStream inputStream = connection.getInputStream();

XmlPullParserFactory xmlFactoryObject = XmlPullParserFactory.newInstance();
XmlPullParser pullParser = xmlFactoryObject.newPullParser();
pullParser.setInput(inputStream, null);
// here comes the actual parsing
like image 930
ElegyD Avatar asked Feb 01 '17 07:02

ElegyD


3 Answers

There are basically two type of parser: SAX and DOM. As per your requirement, where you are updating UITableView as soon as XML element received, It is perfectly clear that you will have to use SAX parser only. (DOM parser parsed the entire document and builds up an in-memory representation that you can query for different elements).

There are two popular SAX parser for iOS as listed below:

1)NSXMLParser (This has been renamed to XMLParser) - included by default with the iPhone SDK.

2)libxml2 - is an Open Source library that is included by default with the iPhone SDK

Apple has made an excellent code sample called XMLPerformance that allows you to compare the time it takes to parse a ~900KB XML document containing the top 300 iTunes songs with both the NSXML and libxml2 APIs.

Ok, here’s a graph that shows the peak memory usage by parser (this was obtained through running the various methods through the Object Allocations tool)

a busy cat

Data clearly shows that libxml2’s SAX method (Which you already tried) is the best option as far as peak memory usage is concerned.

On the other hand, implementing XMLParser is quite easy. Below is my example snippet which i tried using it.

In your class declaration, implement the XMLParserDelegate protocol

class ViewController: UIViewController,XMLParserDelegate {

override func viewDidLoad() {
    super.viewDidLoad()

Use below method to initialize the parser:

func xmlParser()
{
    posts = []
    parser = XMLParser(contentsOf:(NSURL(string:"http://images.apple.com/main/rss/hotnews/hotnews.rss"))! as URL)!
    parser.delegate = self
    parser.parse()
}

Once you initialize the delegate, below delegate methods will get called as and when needed:

func parserDidStartDocument(_ parser: XMLParser) {
    print("started")
}

func parserDidEndDocument(_ parser: XMLParser) {
    print("ended")
}

func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
    print("didEndElement")
}

func parser(_ parser: XMLParser, foundCharacters string: String) {
    print("foundCharacters")
}

There are other delegate methods as well which you can call according to your requirements.

like image 51
Harish Gupta Avatar answered Nov 18 '22 06:11

Harish Gupta


Maybe the client/server has a problem with some Session-Cookies? Try to delete them after each request:

// Loops through each of the cookies and deletes them.
let cookieStore = HTTPCookieStorage.shared
for cookie in cookieStore.cookies ?? [] {
    cookieStore.deleteCookie(cookie)
} 
like image 44
Stefan Avatar answered Nov 18 '22 06:11

Stefan


The below solution may help you out. I have explored about your question and succeed some what as a result below. But I can't able to test the below solution lack of API's. Try with your available Endpoints. Hope it will help you.

func xmlStream() {
        let task =
            URLSession.shared.streamTask(withHostName: "chat.example.com", port: 5555)                                                           
        task.readData(ofMinLength: 16384, maxLength: 65536, timeout: 30.0) { (data, eof, error) in

            let parser = XMLParser(data: data!)
            parser.delegate = self
            if parser.parse() {
                //print result
            }

        }
        task.resume()

    }

//XML parser methods

Moreover this may guide you.

like image 1
Vishnuvardhan Avatar answered Nov 18 '22 07:11

Vishnuvardhan