iOS8 NSXMLParser сбой

У меня произошел сбой в NSXMLParser

* Завершение работы приложения из-за необработанного исключения "NSInternalInconsistencyException", причина: "NSXMLParser не поддерживает повторный вход".

Вот мой код

NSString *wrappedSnippet = [NSString stringWithFormat:@"<html>%@</html>", self.snippet];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:[wrappedSnippet dataUsingEncoding:NSUTF8StringEncoding]];
[parser setDelegate:self];
[parser parse];

приложение вылетает на последней строке.

Обратите внимание, что на iOS7 все отлично работает!

5 ответов

Решение

iOS8 выдает исключение, что предыдущие версии отлавливаются и обрабатываются в фоновом режиме.
см. руководство Начиная с ios 5 NSXMLParser является поточно-ориентированным, но не реентерабельным! убедитесь, что вы не вызываете parse от вашего делегата NSXMLParser. "Я" в вашем случае.

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, ^{ });

Замените свой код на приведенные выше строки, надеюсь, это поможет вам!

Я решил свою проблему, отправив парсер в фоновую очередь!

NSXMLParser теперь потокобезопасен. Тем не менее, он не реентерабелен в данном потоке; не вызывайте -parse для NSXMLParser из обратного вызова делегата другого 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;
          }
     });
}

В этом вопросе это означает, что вы можете повторно вызвать функцию [NSXMLParser parse] в любом его делегате. Иногда вы можете вызвать [parser parse] в parserDidEndDocument:
Но он сообщит вам, что это повторяющаяся ошибка!

Таким образом, решение в том, что вы можете [parser parser] в другой очереди,
например, вы можете сделать это, вызвав dispatch_async(dispatch_get_main_queue(), ^{ do in block});

или вам нужно настроить поток вызовов,
убедитесь, что вы не будете вызывать функцию разбора в делегате.

У меня была та же проблема, и я написал подкласс на основе NSXMLParser, который обрабатывает случай:

      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)
        }


       }
    }

PS Идея в значительной степени совпадает с предложенной @CrimeZone.

Другие вопросы по тегам