Closed alusev closed 6 years ago
I'm using NSXMLParser underneath which, if my understanding is correct, is synchronous. There aren't any other uses of async code that I can think of in parse
either, so I don't think the error is related to async handling unless you're specifically using it in asynchronous code and seeing the error.
Related to the error you're seeing, you mention that you're calling parse
getting blank results back on the 4th or 5th call. Are you passing the same data
instance in each time?
(As an aside, if it is the same data
being passed in, you should be able to get away with only calling parse
once and then saving off the return value.)
You’re right, it is synchronous, then I don’t understand why it may return an empty object. I use the library on iOS for parsing a response from web-service and the response is always relatively small. Each time I do 2 SOAP calls (and then I parse it): 1st time I get a token and the 2nd time I get data that I need. There are 2 different functions within a class. So every time it is a different XMLIndexer object. Sometimes it fails to parse in the first function but it is more common that it fails on the 2nd. Sometimes it fails on the very first call (token + data), and it fails every time until I rerun my app, sometimes it fails only once, another time it fails after 4-5 calls and then it works again.
Do you have any code that I could use to help repro this, like just a snippet?
It's possible there is a bug where the library is incorrectly caching something, but nothing comes to mind right away.
Sure. Here is it.
class Fetcher {
init() {}
func startBalanceRequest(phoneNumber: String, completionHandler: (phoneNumber: String, data: Dictionary<String, AnyObject>?, errorMessage: String?) -> Void) {
let today = dateFormatter.stringFromDate(NSDate())
let envelope = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> ... </soapenv:Envelope>"
let request = NSMutableURLRequest(URL: NSURL(string: WebserviceConstants.WebserviceURL)!)
request.HTTPMethod = "POST"
request.HTTPBody = envelope.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
request.addValue("gzip", forHTTPHeaderField: "Accept-Encoding")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
// debugging
let strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)\n\n")
let xml = SWXMLHash.parse(data)
if xml["soap:Envelope"].element != nil {
// parse all data here
var balance = Dictionary<String, AnyObject>()
if let _ = xml["soap:Envelope"]["soap:Body"][“planDescription"].element {
// <..>
// get details
self.startDetailsRequest(forPhoneNumber: phoneNumber, balance: balance, completionHandler: completionHandler)
} else {
println("Error: XML structure has been changed")
completionHandler(phoneNumber: phoneNumber, data: nil, errorMessage: "App is out of date. Please update.")
}
} else {
println("Error: Internet connection failed")
completionHandler(phoneNumber: phoneNumber, data: nil, errorMessage: "Error: Internet connection failed")
}
})
task.resume()
}
private func startDetailsRequest(forPhoneNumber phoneNumber: String, var balance: Dictionary<String, AnyObject>, completionHandler: (phoneNumber: String, data: Dictionary<String, AnyObject>?, errorMessage: String?) -> Void) {
let today = dateFormatter.stringFromDate(NSDate())
let envelope = "<soapenv:Envelope xmlns:soapenv='http://schemas.xmlsoap.org/soap/envelope/'> ... </soapenv:Envelope>"
let request = NSMutableURLRequest(URL: NSURL(string: WebserviceConstants.WebserviceURL)!)
request.HTTPMethod = "POST"
request.HTTPBody = envelope.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
request.addValue("gzip", forHTTPHeaderField: "Accept-Encoding")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {[weak self] data, response, error -> Void in
// debugging
println("Response: \(response)")
let strData = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Body: \(strData)\n\n")
let xml = SWXMLHash.parse(data)
if let planType = xml["soap:Envelope"]["soap:Body"]["ns1:out"]["isPostpagoOrHybrid"].element {
/// <...>
completionHandler(phoneNumber: phoneNumber, data: balance, errorMessage: nil)
} else {
if let _ = xml["soap:Envelope"].element {
println("Error: XML structure has been changed")
completionHandler(phoneNumber: phoneNumber, data: nil, errorMessage: "App is out of date. Please update.")
} else {
println("Error: Couldn't connect to Iusacell server")
completionHandler(phoneNumber: phoneNumber, data: nil, errorMessage: "Error: Internet connection failed")
}
}
})
task.resume()
}
}
Thanks! Give me some time to take a look at it and see if I can figure out what's going on.
I'm still unable to reproduce this on my own. When I call parse
multiple times, it always seems to work.
Here are a few questions:
startBalanceRequest
and startDetailsRequest
or just startDetailsRequest
?)strData
instance that you retrieved from the NSData
reference to parse
to see if that works?I think the issue has something to do with the data reference.
data
is never empty. But if startBalanceRequest
fails, then
startDetailsRequest
is never called. Sometimes the first function fails, sometimes the other.2 Now runs like clockwork. Thanks. What could be possibly wrong with my data
? If I call the method within 4 hours, it is almost always the same response.
I noticed the second method used a weak reference for the data being passed in whereas the first method didn't use a weak reference. I don't know off hand why that would matter, but it was the only thing I spotted that looked a little different. The only guess I've got is that maybe it is getting cleaned up because it is a weak reference, but I have no idea at the moment.
I guessed that getting a string from it would bypass some of the weak reference issues, though.
It might be worthwhile to investigate weak reference issues in SWXMLHash.
I removed [weak self]
a few days ago, it was a garbage that I left. The method is used to be a little bit different but I changed some code and forgot to remove weak self
. However, the problem didn't go away until I changed parse
’s parameter, like you suggested.
Interesting - so it still happened regardless of using a weak reference. I'll have to do some more research then.
I'm just going through old issues... I have yet to hear of any other issues like this one and I still haven't been able to reproduce it. Have you run into it again or had any similar issues? If not, I'd like to close this issue for the time being. Feel free to let me know either way, though.
(thanks for the report, too!)
It has been a long time since the last time I used the library. I ended up parsing the string.
Since iOS 15 we have asynchronous sequences.
I suggested to Apple via Apple feedback to let us do something like
let (bytes, response) = try await URLSession.shared.bytes(for: urlRequest)
// bytes if of type URLSession.AsyncBytes
let parser = XMLParser(bytes)
parser.delegate = ...
parser.parse()
So that the XMLParser instance can start parsing while the device is in the process of receiving data
If you think this is a good idea, it should help if more people make this suggestion to Apple.
Sometimes “parse” method returns an empty object (every 4-5th execution)
Code:
let xml = SWXMLHash.parse(data)
Log: (lldb) po xml
Element { Element = 0x00007f93f2e18960 { name = "SWXMLHash_Root_Element" text = nil attributes = {} children = 0 values {} count = 0 index = 0 } }
(lldb) po data
<OS_dispatch_data: data[0x7f93f2e29830] = { composite, size = 2031, num_records = 2 record[0] = { from = 0, length = 1297, data_object = 0x7f93f2ccdff0 }, record[1] = { from = 0, length = 734, data_object = 0x7f93f2e157c0 }, }>