更新
使用NSXMLParser
类方法initWithContentsOfURL
时,不是在下载XML提要时进行解析,而是尝试将整个XML文件加载到内存中,然后才启动解析过程。如果XML提供量很大(使用过多的RAM,本身效率低下,因为它不是与下载并行解析,而是只在下载完成后才开始解析等),这是有问题的。
是否有人发现如何使用NSXMLParser
将Feed流式传输到设备时如何解析?是的,您可以使用LibXML2
(如下所述),但似乎应该可以使用NSXMLParser
。但它让我望而却步。
原始问题:
我正在努力使用NSXMLParser
从Web流中读取XML。如果您使用initWithContentsOfURL
,虽然界面可能会导致人们推断它会从Web流式传输XML,但它似乎没有这样做,而是试图先尝试加载整个XML文件任何解析发生。对于适度大小的XML文件来说这很好,但是对于非常大的XML文件来说,这是有问题的。
我已经看到了将NSXMLParser
与initWithStream
结合使用以及从网络流式传输的一些自定义NSInputStream
的讨论。例如,有一些答案建议使用following Cocoa Builder post中引用的CFStreamCreateBoundPair
和Apple 流编程指南中的Setting Up Socket Streams讨论,但我还没有开始工作。我甚至尝试编写自己的子NSInputStream
使用NSURLConnection
(这本身就非常擅长流媒体),但我无法将其与NSXMLParser
结合使用
最后,我决定使用LibXML2
而不是NSXMLParser
,正如Apple XMLPerformance sample中所示,但我想知道是否有人从网络源获取流媒体的运气与NSXMLParser
合作。我已经看到很多“理论上你可以做 x ”的答案,建议从CFStreamCreateBoundPair
到HTTPBodyStream
从NSURLRequest
抓取所有内容,但是我尚未通过NSXMLParser
发现流媒体的工作演示。
Ray Wenderlich文章How To Choose The Best XML Parser for Your iPhone Project似乎确认NSXMLParser
不适合大型XML文件,但有关于基于可能的基于NSXMLParser
的解决方案的所有帖子都适用于流媒体真的很大的XML文件,我很惊讶我还没有找到这方面的工作演示。有谁知道从网络流出的功能NSXMLParser
实现?很明显,我可以坚持使用LibXML2
或其他一些等效的XML解析器,但是使用NSXMLParser
进行流式处理的概念似乎非常接近。
答案 0 :(得分:5)
-[NSXMLParser initWithStream:]
是目前执行数据流式解析的NSXMLParser
的唯一接口。将其连接到以递增方式提供数据的异步NSURLConnection
是不实用的,因为NSXMLParser
采用阻塞,“拉”式方法从NSInputStream
读取。也就是说,-[NSXMLParser parse]
在处理NSInputStream
时会执行以下操作:
while (1) {
NSInteger length = [stream read:buffer maxLength:maxLength];
if (!length)
break;
// Parse data …
}
为了逐步向此解析器提供数据,需要一个自定义NSInputStream
子类,将后台队列上的NSURLConnectionDelegate
调用收到的数据或runloop汇总到-read:maxLength:
调用NSXMLParser
正在等待。
概念验证实现如下:
#include <Foundation/Foundation.h>
@interface ReceivedDataStream : NSInputStream <NSURLConnectionDelegate>
@property (retain) NSURLConnection *connection;
@property (retain) NSMutableArray *bufferedData;
@property (assign, getter=isFinished) BOOL finished;
@property (retain) dispatch_semaphore_t semaphore;
@end
@implementation ReceivedDataStream
- (id)initWithContentsOfURL:(NSURL *)url
{
if (!(self = [super init]))
return nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO] autorelease];
self.connection.delegateQueue = [[[NSOperationQueue alloc] init] autorelease];
self.bufferedData = [NSMutableArray array];
self.semaphore = dispatch_semaphore_create(0);
return self;
}
- (void)dealloc
{
self.connection = nil;
self.bufferedData = nil;
self.semaphore = nil;
[super dealloc];
}
- (BOOL)hasBufferedData
{
@synchronized (self) { return self.bufferedData.count > 0; }
}
#pragma mark - NSInputStream overrides
- (void)open
{
NSLog(@"open");
[self.connection start];
}
- (void)close
{
NSLog(@"close");
[self.connection cancel];
}
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)maxLength
{
NSLog(@"read:%p maxLength:%ld", buffer, maxLength);
if (self.isFinished && !self.hasBufferedData)
return 0;
if (!self.hasBufferedData)
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSAssert(self.isFinished || self.hasBufferedData, @"Was woken without new information");
if (self.isFinished && !self.hasBufferedData)
return 0;
NSData *data = nil;
@synchronized (self) {
data = [[self.bufferedData[0] retain] autorelease];
[self.bufferedData removeObjectAtIndex:0];
if (data.length > maxLength) {
NSData *remainingData = [NSData dataWithBytes:data.bytes + maxLength length:data.length - maxLength];
[self.bufferedData insertObject:remainingData atIndex:0];
}
}
NSUInteger copiedLength = MIN([data length], maxLength);
memcpy(buffer, [data bytes], copiedLength);
return copiedLength;
}
#pragma mark - NSURLConnetionDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"connection:%@ didReceiveData:…", connection);
@synchronized (self) {
[self.bufferedData addObject:data];
}
dispatch_semaphore_signal(self.semaphore);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"connectionDidFinishLoading:%@", connection);
self.finished = YES;
dispatch_semaphore_signal(self.semaphore);
}
@end
@interface ParserDelegate : NSObject <NSXMLParserDelegate>
@end
@implementation ParserDelegate
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
NSLog(@"parser:%@ didStartElement:%@ namespaceURI:%@ qualifiedName:%@ attributes:%@", parser, elementName, namespaceURI, qualifiedName, attributeDict);
}
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
NSLog(@"parserDidEndDocument:%@", parser);
CFRunLoopStop(CFRunLoopGetCurrent());
}
@end
int main(int argc, char **argv)
{
@autoreleasepool {
NSURL *url = [NSURL URLWithString:@"http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml"];
ReceivedDataStream *stream = [[ReceivedDataStream alloc] initWithContentsOfURL:url];
NSXMLParser *parser = [[NSXMLParser alloc] initWithStream:stream];
parser.delegate = [[[ParserDelegate alloc] init] autorelease];
[parser performSelector:@selector(parse) withObject:nil afterDelay:0.0];
CFRunLoopRun();
}
return 0;
}