Как я могу улучшить производительность парсинга XML моего кода iOS? - PullRequest
0 голосов
/ 04 февраля 2012

Возможно, об этом много спрашивали, но я все еще заблудился.Мне нужно проанализировать XML-файл, полученный из API Google Reader.В основном, он содержит объекты, такие как ниже:

<object>
    <string name="id">feed/http://developer.apple.com/news/rss/news.rss</string>
    <string name="title">Apple Developer News</string>
    <list name="categories">
        <object>
            <string name="id">user/17999068807557229152/label/Apple</string>
            <string name="label">Apple</string>
        </object>
    </list>
    <string name="sortid">DB67AFC7</string>
    <number name="firstitemmsec">1317836072018</number>
    <string name="htmlUrl">http://developer.apple.com/news/</string>
</object>

Я пробовал с NSXMLParser, и он работает, но это действительно медленно.Возможно, мой код не самый эффективный, но все же, он может занять более 10 секунд, чтобы проанализировать и сохранить объект в Core Data.Я также посмотрел несколько других библиотек, но их использование кажется таким сложным и тяжелым для такого маленького XML-файла.

Как вы думаете, что я должен использовать?

Спасибо.

РЕДАКТИРОВАТЬ

Здесь код синтаксического анализатора:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {

    if([elementName isEqualToString:@"list"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"subscriptions"]){
        subscriptionListFound = YES;
    }

    if(subscriptionListFound){
        if([elementName isEqualToString:@"list"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"categories"]){
            categoryFound = YES;
            currentCategoryId = [[[NSMutableString alloc] init] autorelease];
            currentCategoryLabel = [[[NSMutableString alloc] init] autorelease];
        }
        if([elementName isEqualToString:@"object"] && !subscriptionFound && !categoryFound){
            subscriptionFound = YES;
            currentSubscriptionTitle = [[[NSMutableString alloc] init] autorelease];
            currentSubscriptionId = [[[NSMutableString alloc] init] autorelease];
            currentSubscriptionHtmlURL = [[[NSMutableString alloc] init] autorelease];
        }
        if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"id"]){
            if(categoryFound){
                categoryIdFound = YES; 
            }
            else{
                subscriptionIdFound = YES;
            }
        }
        if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"title"]){
            subscriptionTitleFound = YES;
        }
        if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"label"]){
            categoryLabelFound = YES;
        }
        if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"htmlUrl"]){
            subscriptionHtmlURLFound = YES;
        }
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {   

    if([elementName isEqualToString:@"list"] && !categoryFound){
        subscriptionListFound = NO;
    }

    if([elementName isEqualToString:@"list"] && categoryFound){
        categoryFound = NO;
    }

    if([elementName isEqualToString:@"object"] && !categoryFound && subscriptionFound){        
        [self saveSubscription];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"currentSubscriptionNotification" object:currentSubscriptionTitle];
        subscriptionFound = NO;
    }

    if([elementName isEqualToString:@"string"]){
        if(subscriptionIdFound == YES) {
            [currentSubscriptionId appendString:self.currentParsedCharacterData];
            subscriptionIdFound = NO;
        }
        if(subscriptionTitleFound == YES) {
            [currentSubscriptionTitle appendString:self.currentParsedCharacterData];
            subscriptionTitleFound = NO;
        }
        if(subscriptionHtmlURLFound == YES) {
            [currentSubscriptionHtmlURL appendString:self.currentParsedCharacterData];
            subscriptionHtmlURLFound = NO;
        }
        if(categoryIdFound == YES) {
            [currentCategoryId appendString:self.currentParsedCharacterData];
            categoryIdFound = NO;
        }
        if(categoryLabelFound == YES) {
            [currentCategoryLabel appendString:self.currentParsedCharacterData];
            categoryLabelFound = NO;
        }
    }

    [self.currentParsedCharacterData setString:@""];
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    [self.currentParsedCharacterData appendString:string];
}

Здесь код для сохранения с помощьюCoreData:

- (void) saveSubscription {

    NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
    [fetchRequest setEntity:
     [NSEntityDescription entityForName:@"Group" inManagedObjectContext:context]];
    [fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(id == %@)",self.currentCategoryId]];
    [fetchRequest setSortDescriptors: [NSArray arrayWithObject:
                                        [[[NSSortDescriptor alloc] initWithKey: @"id"
                                        ascending:YES] autorelease]]];

    NSError *error2 = nil;
    NSArray *foundGroups = [context executeFetchRequest:fetchRequest error:&error2];

    if ([foundGroups count] > 0) {
        self.currentGroupObject = [foundGroups objectAtIndex:0];
    }
    else {
        self.currentGroupObject = [NSEntityDescription insertNewObjectForEntityForName:@"Group" inManagedObjectContext:context];
        [self.currentGroupObject setId:self.currentCategoryId];
        [self.currentGroupObject setLabel:self.currentCategoryLabel];
    }

    fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
    [fetchRequest setEntity:
     [NSEntityDescription entityForName:@"Subscription" inManagedObjectContext:context]];
    [fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(id == %@)", self.currentSubscriptionId]];
    [fetchRequest setSortDescriptors: [NSArray arrayWithObject:
                                       [[[NSSortDescriptor alloc] initWithKey: @"id"
                                                                    ascending:YES] autorelease]]];

    error2 = nil;
    NSArray *foundSubscriptions = [context executeFetchRequest:fetchRequest error:&error2];

    if ([foundSubscriptions count] > 0) {
        self.currentSubscriptionObject = [foundSubscriptions objectAtIndex:0];
    }
    else {
        self.currentSubscriptionObject = [NSEntityDescription insertNewObjectForEntityForName:@"Subscription" inManagedObjectContext:context];
        [self.currentSubscriptionObject setId:self.currentSubscriptionId];
        [self.currentSubscriptionObject setTitle:self.currentSubscriptionTitle];
        [self.currentSubscriptionObject setHtmlURL:self.currentSubscriptionHtmlURL];
        NSString *faviconURL = [self favIconUrlStringFromURL:self.currentSubscriptionHtmlURL];
        NSString *faviconPath = [self saveFavicon:self.currentSubscriptionTitle url:faviconURL];
        [self.currentSubscriptionObject setFaviconPath:faviconPath];
        [self.currentSubscriptionObject setGroup:self.currentGroupObject];
        [self.currentGroupObject addSubscriptionObject:self.currentSubscriptionObject];
    }

    NSError *error;
    if (![context save:&error]) {
        NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
    }
}

Ответы [ 4 ]

9 голосов
/ 04 февраля 2012

Ваша логика синтаксического анализа довольно неэффективна - вы делаете один и тот же тест снова и снова, говоря

if (string and x) do this
if (string and y) do this
if (string and z) do this

Вместо

if (string)
    if (x) do this
    if (y) do this
    if (z) do this

Все эти ненужные сравнения строк, вероятно, и делают ваш анализ таким медленным. То же самое относится ко всем поискам объектов. Если вам нужно значение несколько раз, получите его один раз, а затем сохраните в переменной.

Вызовы метода Objective C относительно медленны и не могут быть оптимизированы компилятором, поэтому, если значение не меняется, вы должны вызвать метод один раз, а затем сохранить его.

Так, например, это:

if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"id"]){
    if(categoryFound){
        categoryIdFound = YES; 
    }
    else{
        subscriptionIdFound = YES;
    }
}
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"title"]){
    subscriptionTitleFound = YES;
}
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"label"]){
    categoryLabelFound = YES;
}
if([elementName isEqualToString:@"string"] && [[attributeDict objectForKey:@"name"] isEqualToString:@"htmlUrl"]){
    subscriptionHtmlURLFound = YES;
}

Можно переписать так:

NSString *name = [attributeDict objectForKey:@"name"];
if([elementName isEqualToString:@"string"])
{
    if ([name isEqualToString:@"id"])
    {
        if(categoryFound){
            categoryIdFound = YES; 
        }
        else{
            subscriptionIdFound = YES;
        }
    }
    else if ([name isEqualToString:@"title"])
    {
        subscriptionTitleFound = YES;
    }
    else if ([name isEqualToString:@"label"])
    {
        categoryLabelFound = YES;
    }
    else if ([name isEqualToString:@"htmlUrl"])
    {
        subscriptionHtmlURLFound = YES;
    }
}

Что гораздо эффективнее.

1 голос
/ 04 февраля 2012

Я предлагаю вам использовать GDataXML.Это довольно просто в использовании и очень быстро.Для получения дополнительной информации вы можете прочитать по адресу как читать-и-писать-xml-documents-with-gdataxml .

Я уже ответил на аналогичный вопрос о том, как читатьатрибут с GDataXML в этом разделе переполнения стека: get-xml-response-value-with-gdataxml .

0 голосов
/ 04 февраля 2012

После разработки нескольких приложений с такими же потребностями, как у вас, я искренне рекомендую AQToolkit

Моя обычная установка для синтаксического анализа XML более или менее похожа на это:

  • Создайте отдельную очередь, используя GCD или NSOperationsQueue
  • Настройте поток ввода с помощью HTTPMessage и AQGZipInputStream

Пример кода:

HTTPMessage *message = [HTTPMessage requestMessageWithMethod:@"GET" url:url version:HTTPVersion1_1];
[message setUseGzipEncoding:YES];       
AQGzipInputStream *inputstream = [[AQGzipInputStream alloc] initWithCompressedStream:         [message inputStream]];
  • Передать поток отдельному делегату синтаксического анализатора, который создает отдельный NSManagedObjectContext и объединяет изменения в основной NSManagedObjectContext при сохранении (NSManagedObject не является потокобезопасным!)

Пример кода для инициализации контекстаи добавление уведомлений о слиянии:

-(void)parserDidStartDocument:(AQXMLParser *)parser
{
  self.ctx=[[NSManagedObjectContext alloc] init];
  [self.ctx setMergePolicy: NSMergeByPropertyObjectTrumpMergePolicy];
  [self.ctx setPersistentStoreCoordinator: [Database db].persistentStoreCoordinator];
  NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
  [dnc addObserver:self selector:@selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:self.ctx];  
  parsedElements = 0;
}

- (void)mergeContextChanges:(NSNotification *)notification{
  SEL selector = @selector(mergeHelper:);
  [self performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}

- (void)mergeHelper:(NSNotification*)saveNotification
{
// Fault in all updated objects
NSArray* updates = [[saveNotification.userInfo objectForKey:@"updated"] allObjects];
for (NSInteger i = [updates count]-1; i >= 0; i--)
{
    [[[Database db].managedObjectContext objectWithID:[[updates objectAtIndex:i] objectID]] willAccessValueForKey:nil];
}

// Merge
[[Database db].managedObjectContext    mergeChangesFromContextDidSaveNotification:saveNotification];
}

На мой взгляд, выбор правильного парсера более важен для огромных наборов данных.Если ваш набор данных управляем, то у вас есть много преимуществ от достойной реализации.Использование любого синтаксического анализатора на основе libxml и синтаксический анализ фрагментов данных по мере их получения даст вам значительное повышение производительности от анализа данных после их загрузки.

В зависимости от вашего источника данных libz может выдать Z_BUF_ERROR (по крайней мере, в симуляторе).Я предложил решение в pull-запросе на AQToolkit, но я вполне уверен, что будут еще лучшие решения!

0 голосов
/ 04 февраля 2012

На мой взгляд, лучшая библиотека для разбора XML на iOS - TouchXML . Он позволяет вам анализировать XML с помощью xPaths и имеет расширенные параметры синтаксического анализа элементов. С этим вы также можете анализировать документы XHTML.

Парсинг очень прост:

NSData *xmlData = read your xml file
CXMLDocument *doc = [[CXMLDocument alloc] initWithData:xmlData options:0 error:nil]
NSArray *objects = [doc nodesForXPath:@"//object" error:nil];

for (CXMLElement *object in objects) {
    NSArray *children = [object children];
    for(CXMLElement *child in children) {
        if([[child name] isEqualToString:@"string"]) {
            // you are parsing <string> element.
            // you can obtain element attribute by:
            NSString *name = [[child attributeForName:@"name"] stringValue];
            // you can obtain string between <></> tags via:
            NSString *value = [child stringValue];
        } else if([[child name] isEqualToString:@"list"]) {
            // you are parsing <list> element.
        } else if ... 
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...