- [NSXMLParser initWithStream:]
是当前执行流式解析数据的NSXMLParser
的唯一接口。将其连接到提供增量数据的异步NSURLConnection
上是棘手的,因为NSXMLParser
采用阻塞、“拉取”式的方法从NSInputStream
中读取数据。也就是说,当处理NSInputStream
时,- [NSXMLParser parse]
会执行以下操作:
while (1) {
NSInteger length = [stream read:buffer maxLength:maxLength];
if (!length)
break;
// Parse data …
}
为了逐步向此解析器提供数据,需要一个自定义的
NSInputStream
子类,该子类将在后台队列或运行循环上通过
NSURLConnectionDelegate
调用接收到的数据转发到
-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;
}
NSURLConnection
作为流(遵循我在原始问题中引用的示例)时,我从未使其正常工作。我看到了很多帖子和文章,其中包含假设性的“好吧,你应该能够做X”的内容,但我从未找到一个可行的示例,并且我自己也从未能够让它正常工作。您是否知道任何将initWithStream
与NSURLConnection
结合使用的可行示例? - RobNSXMLParser
从流中读取时,并没有旋转运行循环,所以你必须配置NSURLConnection
将委托消息发送到辅助线程或调度队列。一旦意识到了这一点,就只需要简单地打字即可 :) - bdashread:maxLength:
在didReceiveData
发送信号量时总是等待信号量,那么此代码才能正常工作。但是,如果didReceiveData
在read:maxLength:
调用dispatch_semaphore_wait
之前填充了bufferedData
并发送了信号量,则read:maxLength:
将无法删除信号量,并且在下一次调用read:maxLength
时,当它去等待信号量时,先前的信号量仍然存在,这段代码就会崩溃。 - Rob