I had a crash in NSXMLParser
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSXMLParser does not support reentrant parsing.'
Here is my code
NSString *wrappedSnippet = [NSString stringWithFormat:@"<html>%@</html>", self.snippet];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:[wrappedSnippet dataUsingEncoding:NSUTF8StringEncoding]];
[parser setDelegate:self];
[parser parse];
app crashes on the last line.
Note, that everything works perfect on iOS7!
iOS8 throws an exception that previous versions caught and handled in the background.
see manual As from ios 5 NSXMLParser is thread safe but not reentrant! make sure you aren't calling parse from your NSXMLParser delegate. "Self" in your case.
dispatch_queue_t reentrantAvoidanceQueue = dispatch_queue_create("reentrantAvoidanceQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(reentrantAvoidanceQueue, ^{
NSXMLParser* parser = [[NSXMLParser alloc] initWithData:xml];
[parser setDelegate:self];
if (![parser parse]) {
NSLog(@"There was an error=%@ parsing the xml. with data %@", [parser parserError], [[NSString alloc] initWithData:xml encoding: NSASCIIStringEncoding]);
}
[parser release];
});
dispatch_sync(reentrantAvoidanceQueue, ^{ });
Replace your code with above lines, Hope it helps you!
I resolved my problem by dispatching parser in background queue!
NSXMLParser is now threadsafe. However, it is not reentrant on a given thread; don't call -parse on an NSXMLParser from within a delegate callback of another NSXMLParser.
- (void)parseWithCompletion:(ParserHandler)handler {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
self.handler = handler;
[self parse];
});
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.handler) {
self.handler(YES, self.dictionary, nil);
self.handler = nil;
}
});
}
In this issue, that means you could re-call [NSXMLParser parse] function in anything of his delegate.
Sometimes you may call [parser parse] in parserDidEndDocument:
But it will notify you that this's a reentrant error!
So, the solution is, either you could [parser parser] in different queue,
for example, you could do it via calling dispatch_async(dispatch_get_main_queue(), ^{ do in block});
or, you need to adjust your call flow,
make sure that you won't call parse function in delegate.
I had the same problem and wrote a subclass based upon NSXMLParser which handles the case:
class SynchronizedXMLParser: NSXMLParser
{
// shared queue
private static let _serialQueue: NSOperationQueue = {
let queue = NSOperationQueue()
queue.qualityOfService = NSQualityOfService.UserInitiated
// making it serial on purpose in order to avoid
// the "reentrant parsing" issue
queue.maxConcurrentOperationCount = 1
return queue
}()
// instance level convenience accessor
private var _serialQueue: NSOperationQueue
{
get
{
return self.dynamicType._serialQueue
}
}
private weak var _associatedParsingTask: NSBlockOperation?
deinit
{
_associatedParsingTask?.cancel()
}
//MARK: - Overridden
required override init(data: NSData)
{
super.init(data: data)
}
// still unsafe to call within the delegate callbacks
override func parse() -> Bool
{
var parsingResult = false
if (_associatedParsingTask == nil)
{
let parsingTask = NSBlockOperation(block: {() -> Void in
parsingResult = super.parse()
})
_associatedParsingTask = parsingTask
// making it synchronous in order to return the result
// of the super's parse call
_serialQueue.addOperations([parsingTask], waitUntilFinished: true)
}
return parsingResult
}
override func abortParsing()
{
if let parsingTask = _associatedParsingTask
{
parsingTask.cancel()
_associatedParsingTask = nil
}
super.abortParsing()
}
// MARK: - Introduced
// safe to use everywhere as it doesn't force the calling thread to wait until this me
thod returns
func parse(completion completion:(Bool) -> Void) -> Void
{
var parsingResult = false
if (_associatedParsingTask == nil)
{
let parsingTask = NSBlockOperation(block: {() -> Void in
parsingResult = super.parse()
})
parsingTask.completionBlock = { () -> Void in
completion(parsingResult)
}
_associatedParsingTask = parsingTask
// making it synchronous in order to return the result
// of the super's parse call
_serialQueue.addOperation(parsingTask)
}
}
}
P.S. The idea is pretty much the same as what @CrimeZone suggested.