问题
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!
回答1:
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.
回答2:
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!
回答3:
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;
}
});
}
回答4:
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.
回答5:
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.
来源:https://stackoverflow.com/questions/25363560/ios8-nsxmlparser-crash