9

アップデート:

NSXMLParserクラス メソッドを使用するinitWithContentsOfURLと、XML フィードのダウンロード時に解析するのではなく、XML ファイル全体をメモリにロードしてから解析プロセスを開始しようとするように見えます。これは、XML フィードが大きい場合に問題になります (大量の RAM を使用している、ダウンロードと並行して解析するのではなく、ダウンロードが完了してから解析を開始するため、本質的に非効率的であるなど)。

を使用してフィードがデバイスにストリーミングされているときに解析する方法を発見した人はいNSXMLParserますか? はい、LibXML2(以下で説明するように)使用できますが、 で実行できるはずNSXMLParserです。しかし、それは私を逃れています。

元の質問:

私はNSXMLParser、Web ストリームから XML を読み取るために使用することに取り組んでいました。を使用する場合initWithContentsOfURL、インターフェイスから Web から XML をストリーミングすることが推測される可能性がありますが、そうではないように見えますが、解析が行われる前に最初に XML ファイル全体を読み込もうとしているように見えます。適度なサイズの XML ファイルの場合は問題ありませんが、非常に大きな XML ファイルの場合は問題があります。

Web からストリーミングされているカスタマイズされたものNSXMLParserと組み合わせて使用​​するという議論を見てきました。たとえば、次の Cocoa Builder の投稿やApple Stream Programming GuideのSetup Socket Streamsの説明で言及されているようなものを使用することを提案する回答がありましたが、私はそれを機能させていません。を使用する独自のサブクラスを作成しようとしましたが (それ自体は、ストリーミングがかなり得意です)、 と組み合わせて動作させることはできませんでした。initWithStreamNSInputStreamCFStreamCreateBoundPairNSInputStreamNSURLConnectionNSXMLParser

最終的に、Apple XMLPerformance サンプルLibXML2で示されているように、ではなくを使用することにしましたが、. からを取得するまでのすべてを示唆する「理論的には x を実行できます」という種類の回答をたくさん見てきましたが、 を使用したスト​​リーミングの実際デモにまだ遭遇していません。NSXMLParserNSXMLParserCFStreamCreateBoundPairHTTPBodyStreamNSURLRequestNSXMLParser

Ray Wenderlich の記事How To Choose The Best XML Parser for Your iPhone ProjectNSXMLParserは、それが大きな XML ファイルにはあま​​り適していないことを確認しているようですが、非常に大きな XML ファイルNSXMLParserをストリーミングするための可能なベースの回避策についてのすべての投稿で、私は'驚いたことに、これの実際のデモンストレーションをまだ見つけていません。NSXMLParserWeb からストリーミングする機能する実装を知っている人はいますか? 明らかに、私は、LibXML2または他の同等の XML パーサーを使用することができますが、ストリーミングの概念は、興味をNSXMLParserそそられるほど近いようです。

4

1 に答える 1

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-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;
}
于 2013-01-26T20:06:48.733 に答える