Использование NSXMLParser initWithStream: методы делегата синтаксического анализатора не получены

Основная проблема, над которой я работаю, заключается в использовании классов NSStream для разбора входящих добавочных данных XML. Данные никогда не являются полным XML-документом, но я хочу получать и обрабатывать их поэтапно, в зависимости от того, сколько сокет может прочитать.

Глядя на документацию для NSXMLParser, кажется, что метод initWithStream: для инициализации NSXMLParser был бы идеальным решением моей проблемы. Я могу инициализировать синтаксический анализатор с помощью NSInputStream, а затем вызывать метод parse для NSXMLParser всякий раз, когда я получаю данные через свой сокет, который, в свою очередь, должен вызывать делегатов NSXMLParser.

Однако я не вижу ни одного из вызываемых делегатов, единственный вызываемый метод, который я вижу, - это делегат потока stream:handleEvent:. Кажется, что примеров этого API от Apple или других разработчиков практически нет. Любые идеи о том, что я делаю неправильно или как правильно использовать initWithStream:?

ContentParser.h

@interface ContentParser : NSObject <NSStreamDelegate, 
                                     NSXMLParserDelegate>
{
   NSInputStream *inputStream;
   NSOutputStream *outputStream;
   NSMutableData *receivedData;
   NSXMLParser *xmlParser;
}
- (void)initStream;

ContentParser.m

@implementation ContentParser

- (void)initStream
{    
   CFReadStreamRef readStream;
   CFWriteStreamRef writeStream;

   CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, 
                                     (CFStringRef)@"<hostname>", 
                                     <port>, 
                                     &readStream, 
                                     &writeStream);

   inputStream = (__bridge NSInputStream *)readStream;
   outputStream = (__bridge NSOutputStream *)writeStream;

   inputStream.delegate = self;
   outputStream.delegate = self;

   [inputStream  scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSDefaultRunLoopMode];
   [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
                           forMode:NSDefaultRunLoopMode];

   [inputStream open];
   [outputStream open];

   xmlParser = [[NSXMLParser alloc] initWithStream:inputStream];
   [xmlParser setDelegate:self];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
                                        namespaceURI:(NSString *)namespaceURI 
                                       qualifiedName:(NSString *)qName 
                                          attributes:(NSDictionary *)attributeDict
{
   NSLog(@"didStartElement: %@, attributeDict: %@", elementName, attributeDict);
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
   NSLog(@"foundCharacters: %@", string);
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
                                      namespaceURI:(NSString *)namespaceURI 
                                     qualifiedName:(NSString *)qName
{
   NSLog(@"didEndElement: %@", elementName);
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
   NSLog(@"Error %ld, Description: %@, Line: %ld, Column: %ld", 
      [parseError code], [[parser parserError] localizedDescription], 
      [parser lineNumber], [parser columnNumber]);
}


- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
   switch (eventCode) {
       case NSStreamEventHasBytesAvailable:
       {
           if (stream == inputStream) {
               NSInputStream *is = (NSInputStream *)stream;
               if (receivedData == nil) {
                   receivedData = [[NSMutableData alloc] init];
               }

               uint8_t buf[1024];
               NSInteger bytesRead = [is read:buf maxLength:1024];
               if (bytesRead == -1) {
                  NSLog(@"Network read error");
               } else if (bytesRead == 0) {
                  NSLog(@"No buffer received");
               } else {
                  [receivedData appendBytes:buf length:bytesRead];
                  BOOL parserResult = [xmlParser parse];
                  if (parserResult == NO) {
                     NSLog(@"Unable to parse XML");
                  }
               }
           }
           break;
       }
       default:
          break;
    }
}

@end

person John Drake    schedule 27.05.2012    source источник
comment
Что ж, Джон, я никогда не пользовался NSXMLParser, потому что ненавижу его. Слишком код, меньше ответа. Проверьте Hpple и мой ответ на этот вопрос. Это лучше, но вы можете использовать то, что хотите. :-)   -  person    schedule 28.05.2012
comment
Я быстро взглянул на Hpple, и оказалось, что он не поддерживает синтаксический анализ данных, поступающих через NSInputStream. Существуют альтернативные решения, например, использование libxml2 напрямую, однако я надеялся, что просто неправильно использовал NSXMLParser.   -  person John Drake    schedule 28.05.2012
comment
О, это правда. Извините, я хотел бы помочь.   -  person    schedule 28.05.2012
comment
Когда вы передаете NSXMLParser поток, вам не нужно вызывать -parse, вы можете просто зависеть от потока для передачи данных в анализатор. Но если вы получаете данные в -stream:handleEvent:, то эти данные не передаются нижестоящему синтаксическому анализатору (NSXMLParser), так как вы получаете поток, а не NSXMLParser. Вы можете проверить cocoabuilder. ком/архив/какао/   -  person gaige    schedule 28.05.2012
comment
Я сам думал так же и пытался не делать ContentParser делегатом NSInputStream, а также удалять вызов -[NSXMLParser parse]. Даже после этих изменений делегаты NSXMLParser не вызываются.   -  person John Drake    schedule 28.05.2012


Ответы (2)


Я понял, в чем проблема, и ответил на нее здесь, если кто-то еще столкнется с этой проблемой в будущем, поскольку у +[NSXMLParser initWithStream] не так много документации.

Мне нужно было вызвать -[NSXMLParser parse] сразу после того, как я выделю NSXMLParser и назначу себя делегатом. Но поскольку это синхронная функция, мне нужно вызвать ее другим потоком, чтобы я не блокировал текущий поток, и он мог получать события NSStream. Мне также не нужно делать себя делегатом для NSInputStream.

Это можно сделать очень просто, используя Grand Central Dispatch (GCD) следующим образом:

// alloc and init the xml parser
xmlParser = [[NSXMLParser alloc] initWithStream:inputStream];
[xmlParser setDelegate:self];

// block to execute
dispatch_block_t dispatch_block = ^(void)
{
    [xmlParser parse];
};

// create a queue with a unique name
dispatch_queue_t dispatch_queue = dispatch_queue_create("parser.queue", NULL);

// dispatch queue
dispatch_async(dispatch_queue, dispatch_block);

// cleanup
dispatch_release(dispatch_queue);

И вот полный рабочий пример, на всякий случай, если кто-то не смог следовать тому, что я написал выше.

ContentParser.h

@interface ContentParser : NSObject <NSStreamDelegate, 
                                     NSXMLParserDelegate>
{
   NSInputStream *inputStream;
   NSOutputStream *outputStream;
   NSMutableData *receivedData;
   NSXMLParser *xmlParser;
}
- (void)initStream;

ContentParser.m

@implementation ContentParser

- (void)initStream
{    
   CFReadStreamRef readStream;
   CFWriteStreamRef writeStream;

   CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, 
                                     (CFStringRef)@"<hostname>", 
                                     <port>, 
                                     &readStream, 
                                     &writeStream);

   inputStream = (__bridge NSInputStream *)readStream;
   outputStream = (__bridge NSOutputStream *)writeStream;

   outputStream.delegate = self;

   [inputStream  scheduleInRunLoop:[NSRunLoop currentRunLoop]
                           forMode:NSDefaultRunLoopMode];
   [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
                           forMode:NSDefaultRunLoopMode];

   [inputStream open];
   [outputStream open];

   xmlParser = [[NSXMLParser alloc] initWithStream:inputStream];
   [xmlParser setDelegate:self];

   dispatch_block_t dispatch_block = ^(void)
   {
      [xmlParser parse];
   };
   dispatch_queue_t dispatch_queue = dispatch_queue_create("parser.queue", NULL);
   dispatch_async(dispatch_queue, dispatch_block);
   dispatch_release(dispatch_queue);
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName 
                                        namespaceURI:(NSString *)namespaceURI 
                                       qualifiedName:(NSString *)qName 
                                      attributes:(NSDictionary *)attributeDict
{
   dispatch_block_t dispatch_block = ^(void)
   {
      NSLog(@"didStartElement: %@, attributeDict: %@", 
         elementName, attributeDict);
   };
   dispatch_queue_t main_queue = dispatch_get_main_queue();
   dispatch_async(main_queue, dispatch_block);
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
   dispatch_block_t dispatch_block = ^(void)
   {
      NSLog(@"foundCharacters: %@", string);
   };
   dispatch_queue_t main_queue = dispatch_get_main_queue();
   dispatch_async(main_queue, dispatch_block);
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
                                      namespaceURI:(NSString *)namespaceURI 
                                     qualifiedName:(NSString *)qName
{
   dispatch_block_t dispatch_block = ^(void)
   {
      NSLog(@"didEndElement: %@", elementName);
   };
   dispatch_queue_t main_queue = dispatch_get_main_queue();
   dispatch_async(main_queue, dispatch_block);
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
   dispatch_block_t dispatch_block = ^(void)
   {
      NSLog(@"Error %ld, Description: %@, Line: %ld, Column: %ld", 
         [parseError code], [[parser parserError] localizedDescription], 
         [parser lineNumber], [parser columnNumber]);
   };
   dispatch_queue_t main_queue = dispatch_get_main_queue();
   dispatch_async(main_queue, dispatch_block);
}   

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
   switch (eventCode) {
      case NSStreamEventHasSpaceAvailable:
      {
         /* write bytes to socket */
         break;
      }
      default:
         break;
    }
}

@end
person John Drake    schedule 31.05.2012

Я не уверен в этом, но я не думаю, что вам нужно отправлять parse каждый раз, когда поток сообщает о событии - документы initWithStream: подразумевают, что парсер сам будет обрабатывать ввод. Я думаю, вы бы просто сделали:

xmlParser = [[NSXMLParser alloc] initWithStream:inputStream];
[xmlParser setDelegate:self];
[xmlParser parse];

и не беспокойтесь о методе делегата потока. Когда вы получите parserDidEndDocument:, вы можете закрыть поток.

person jscs    schedule 27.05.2012
comment
Я тоже это пробовал, делегаты NSXMLParser все еще не вызываются. - person John Drake; 28.05.2012
comment
Хм. По потоку определенно поступают достоверные данные? - person jscs; 28.05.2012
comment
Да, я вижу действительный XML, полученный в stream:handleEvent: при отладке с помощью операторов печати gdb и/или NSLog. - person John Drake; 28.05.2012