Използване на 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

Невиртуалните методи се обвързват по време на компилиране, а не по време на изпълнение. По време на компилиране всичко, което може да се гарантира, е, че T е Base, така че компилаторът обвързва инструмента за достъп на свойството към Base версията на CorrectName и това няма да се промени по време на изпълнение. Вие извиквате метода с Derived като параметър на типа T, но други биха могли да го извикат с друг тип, наследен от Base или дори от самия Base.

Виртуалните методи обаче ще проверят действителния тип време на изпълнение и ще извикат правилния заменен метод.

„Таблицата на истината“, която публикувахте, е неуместна тук, тъй като всички те се оценяват по време на изпълнение. За да докажете, че компилаторът оценява T така, сякаш е Base, опитайте следното:

T t = default(T);
object o = t; // works
Base b = t; // works
Derived d = t; // doesn't work
  -  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. com/archive/cocoa/   -  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